diff options
479 files changed, 16230 insertions, 13436 deletions
diff --git a/.eslintrc.json b/.eslintrc.json deleted file mode 100644 index ac1a4e63..00000000 --- a/.eslintrc.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "env": { - "es2021": true, - "node": true - }, - "extends": "eslint:recommended", - "parserOptions": { - "ecmaVersion": "latest", - "sourceType": "module" - }, - "rules": { - "indent": ["off"], - "no-empty": ["error", { - "allowEmptyCatch": true - }], - "no-unexpected-multiline": ["off"], - "no-unused-labels": ["off"], - "no-unused-vars": ["error", { - "argsIgnorePattern": "^_", - "destructuredArrayIgnorePattern": "^" - }], - "no-cond-assign": ["off"], - "no-constant-condition": ["off"], - "no-unsafe-finally": ["off"] - } -} diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 00000000..9d969f57 --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,139 @@ +import * as path from 'node:path'; +import {fileURLToPath} from 'node:url'; + +import {defineConfig} from 'eslint/config'; +import js from '@eslint/js'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); + +const CLIENT_JAVASCRIPT_PATHS = [ + 'src/static/js/**/*.js', +]; + +const WEB_WORKER_PATHS = [ + 'src/static/js/search-worker.js', +]; + +const COMMON_GLOBALS = { + supportedWhenever: { + URL: 'readonly', + + clearInterval: 'readonly', + clearTimeout: 'readonly', + console: 'readonly', + fetch: 'readonly', + setInterval: 'readonly', + setTimeout: 'readonly', + structuredClone: 'readonly', + }, +}; + +export default defineConfig([ + // basic config + + { + files: ['src/**/*.js'], + extends: ['js/recommended'], + plugins: {js}, + + rules: { + indent: ['off'], + + 'no-empty': ['error', { + allowEmptyCatch: true, + }], + + 'no-unexpected-multiline': ['off'], + 'no-unused-labels': ['off'], + + 'no-unused-vars': ['error', { + argsIgnorePattern: '^_', + destructuredArrayIgnorePattern: '^', + }], + + 'no-cond-assign': ['off'], + 'no-constant-condition': ['off'], + 'no-unsafe-finally': ['off'], + 'no-self-assign': ['off'], + 'require-yield': ['off'], + }, + }, + + // node.js + + { + files: ['src/**/*.js'], + ignores: [...CLIENT_JAVASCRIPT_PATHS], + + languageOptions: { + ecmaVersion: 2022, + sourceType: 'module', + + globals: { + ...COMMON_GLOBALS.supportedWhenever, + + process: 'readonly', + setImmediate: 'readonly', + }, + }, + }, + + // client javascript - all + + { + files: [...CLIENT_JAVASCRIPT_PATHS], + + languageOptions: { + ecmaVersion: 2022, + sourceType: 'module', + + globals: { + ...COMMON_GLOBALS.supportedWhenever, + + CustomEvent: 'readonly', + Response: 'readonly', + Worker: 'readonly', + XMLHttpRequest: 'readonly', + }, + }, + }, + + // client javascript - not web workers + + { + files: [...CLIENT_JAVASCRIPT_PATHS], + ignores: [...WEB_WORKER_PATHS], + + languageOptions: { + globals: { + DOMException: 'readonly', + DOMRect: 'readonly', + MutationObserver: 'readonly', + IntersectionObserver: 'readonly', + + document: 'readonly', + getComputedStyle: 'readonly', + history: 'readonly', + location: 'readonly', + localStorage: 'readonly', + sessionStorage: 'readonly', + window: 'readonly', + }, + }, + }, + + // client javascript - web workers + + { + files: [WEB_WORKER_PATHS], + + languageOptions: { + globals: { + onerror: 'writeable', + onmessage: 'writeable', + onunhandledrejection: 'writeable', + postMessage: 'readonly', + }, + }, + }, +]); diff --git a/package-lock.json b/package-lock.json index 11bc6a41..7cd27198 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,31 +9,32 @@ "version": "0.1.0", "license": "MIT", "dependencies": { - "@js-temporal/polyfill": "^0.4.4", - "chroma-js": "^2.4.2", - "command-exists": "^1.2.9", - "compress-json": "^3.0.5", - "eslint": "^8.37.0", - "flexsearch": "^0.7.43", - "he": "^1.2.0", - "image-size": "^1.0.2", - "js-yaml": "^4.1.0", - "marked": "^10.0.0", - "msgpackr": "^1.10.2", - "rimraf": "^5.0.7", - "striptags": "^4.0.0-alpha.4", - "word-wrap": "^1.2.3" + "@eslint/js": "9.37.0", + "@js-temporal/polyfill": "0.4.4", + "chroma-js": "2.4.2", + "command-exists": "1.2.9", + "compress-json": "3.4.0", + "eslint": "9.37.0", + "flexsearch": "0.7.43", + "image-size": "2.0.2", + "js-yaml": "4.1.0", + "marked": "16.4.1", + "msgpackr": "1.11.1", + "rimraf": "5.0.7", + "striptags": "4.0.0-alpha.4", + "word-count": "0.3.1", + "word-wrap": "1.2.5" }, "bin": { "hsmusic": "src/upd8.js" }, "devDependencies": { "chokidar": "^3.5.3", - "tap": "^19.0.2", + "tap": "^21.1.1", "tcompare": "^6.0.0" }, "engines": { - "node": ">= 20.9.0" + "node": ">= 22.13.0" } }, "node_modules/@alcalzone/ansi-tokenize": { @@ -41,6 +42,7 @@ "resolved": "https://registry.npmjs.org/@alcalzone/ansi-tokenize/-/ansi-tokenize-0.1.3.tgz", "integrity": "sha512-3yWxPTq3UQ/FY9p1ErPxIyfT64elWaMvM9lIHnaqpyft63tkxodF5aUElYHrdisWve5cETkh1+KBw1yJuW0aRw==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^6.2.1", "is-fullwidth-code-point": "^4.0.0" @@ -50,10 +52,11 @@ } }, "node_modules/@alcalzone/ansi-tokenize/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==", + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", "dev": true, + "license": "MIT", "engines": { "node": ">=12" }, @@ -68,16 +71,21 @@ "dev": true }, "node_modules/@bcoe/v8-coverage": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", - "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", - "dev": true + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", + "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } }, "node_modules/@cspotcode/source-map-support": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", "dev": true, + "license": "MIT", "dependencies": { "@jridgewell/trace-mapping": "0.3.9" }, @@ -86,36 +94,92 @@ } }, "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", - "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", + "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", + "license": "MIT", "dependencies": { - "eslint-visitor-keys": "^3.3.0" + "eslint-visitor-keys": "^3.4.3" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, + "funding": { + "url": "https://opencollective.com/eslint" + }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/@eslint-community/regexpp": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.5.0.tgz", - "integrity": "sha512-vITaYzIcNmjn5tF5uxcZ/ft7/RXGrMUIS9HalWckEOF6ESiwXKoMzAQf2UW0aVd6rnOeExTJVd5hmWXucBKGXQ==", + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "license": "MIT", "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, + "node_modules/@eslint/config-array": { + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz", + "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==", + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.6", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.0.tgz", + "integrity": "sha512-WUFvV4WoIwW8Bv0KeKCIIEgdSiFOsulyN0xrMu+7z43q/hkOLXjvb5u7UC9jDxvRzcrbEmuZBX5yJZz1741jog==", + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.16.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.16.0.tgz", + "integrity": "sha512-nmC8/totwobIiFcGkDza3GIKfAw1+hLiYVrh3I1nIomQ8PEr5cxg34jnkmGawul/ep52wGRAcyeDCNtWKSOj4Q==", + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, "node_modules/@eslint/eslintrc": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.2.tgz", - "integrity": "sha512-3W4f5tDUra+pA+FzgugqL2pRimUTDJWKr7BINqOpkZrC0uYI0NIc0/JFgBROCU07HR6GieA5m3/rsPIhDmCXTQ==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", + "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", + "license": "MIT", "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", - "espree": "^9.5.1", - "globals": "^13.19.0", + "espree": "^10.0.1", + "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", @@ -123,31 +187,79 @@ "strip-json-comments": "^3.1.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://opencollective.com/eslint" } }, "node_modules/@eslint/js": { - "version": "8.37.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.37.0.tgz", - "integrity": "sha512-x5vzdtOOGgFVDCUs81QRB2+liax8rFg3+7hqM+QhBG0/G3F1ZsoYl97UrqgHgQ9KKT7G6c4V+aTUCgu/n22v1A==", + "version": "9.37.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.37.0.tgz", + "integrity": "sha512-jaS+NJ+hximswBG6pjNX0uEJZkrT0zwpVi3BA3vX22aFGjJjmgSTSmPpZCRKmoBL5VY/M6p0xsSJx7rk7sy5gg==", + "license": "MIT", "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", + "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.0.tgz", + "integrity": "sha512-sB5uyeq+dwCWyPi31B2gQlVlo+j5brPlWx4yZBrEaRo/nhdDE8Xke1gsGgtiBdaBTxuTkceLVuVt/pclrasb0A==", + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.16.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" } }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.11.8", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", - "integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==", + "node_modules/@humanfs/node": { + "version": "0.16.6", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", + "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", + "license": "Apache-2.0", "dependencies": { - "@humanwhocodes/object-schema": "^1.2.1", - "debug": "^4.1.1", - "minimatch": "^3.0.5" + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.3.0" }, "engines": { - "node": ">=10.10.0" + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", + "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" } }, "node_modules/@humanwhocodes/module-importer": { @@ -162,10 +274,41 @@ "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==" + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@isaacs/balanced-match": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", + "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@isaacs/brace-expansion": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz", + "integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@isaacs/balanced-match": "^4.0.1" + }, + "engines": { + "node": "20 || >=22" + } }, "node_modules/@isaacs/cliui": { "version": "8.0.2", @@ -208,11 +351,25 @@ "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@isaacs/ts-node-temp-fork-for-pr-2009": { "version": "10.9.7", "resolved": "https://registry.npmjs.org/@isaacs/ts-node-temp-fork-for-pr-2009/-/ts-node-temp-fork-for-pr-2009-10.9.7.tgz", "integrity": "sha512-9f0bhUr9TnwwpgUhEpr3FjxSaH/OHaARkE2F9fM0lS4nIs2GNerrvGwQz493dk0JKlTaGYVrKbq36vA/whZ34g==", "dev": true, + "license": "MIT", "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node14": "*", @@ -253,6 +410,7 @@ "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", "dev": true, + "license": "BSD-3-Clause", "engines": { "node": ">=0.3.1" } @@ -262,6 +420,7 @@ "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -271,21 +430,24 @@ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.0.0" } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", - "dev": true + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.9", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", "dev": true, + "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" @@ -375,43 +537,12 @@ "win32" ] }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, "node_modules/@npmcli/agent": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-2.2.2.tgz", - "integrity": "sha512-OrcNPXdpSl9UX7qPVRWbmWMCSXrcDa2M9DvrbOTj7ao1S4PlqVFYv9/yLKMkrJKZ/V5A/kDBC690or307i26Og==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-3.0.0.tgz", + "integrity": "sha512-S79NdEgDQd/NGCay6TCoVzXSj74skRZIKJcpJjC5lOq34SZzyI6MqtiiWoiVWoVrTcGjNeC4ipbh1VIHlpfF5Q==", "dev": true, + "license": "ISC", "dependencies": { "agent-base": "^7.1.0", "http-proxy-agent": "^7.0.0", @@ -420,38 +551,40 @@ "socks-proxy-agent": "^8.0.3" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/@npmcli/fs": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-3.1.1.tgz", - "integrity": "sha512-q9CRWjpHCMIh5sVyefoD1cA7PkvILqCZsnSOEUUivORLjxCO/Irmue2DprETiNgEqktDBZaM1Bi+jrarx1XdCg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-4.0.0.tgz", + "integrity": "sha512-/xGlezI6xfGO9NwuJlnwz/K14qD1kCSAGtacBHnGzeAIuJGazcp45KP5NuyARXoKb7cwulAGWVsbeSxdG/cb0Q==", "dev": true, + "license": "ISC", "dependencies": { "semver": "^7.3.5" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/@npmcli/git": { - "version": "5.0.7", - "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-5.0.7.tgz", - "integrity": "sha512-WaOVvto604d5IpdCRV2KjQu8PzkfE96d50CQGKgywXh2GxXmDeUO5EWcBC4V57uFyrNqx83+MewuJh3WTR3xPA==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-7.0.0.tgz", + "integrity": "sha512-vnz7BVGtOctJAIHouCJdvWBhsTVSICMeUgZo2c7XAi5d5Rrl80S1H7oPym7K03cRuinK5Q6s2dw36+PgXQTcMA==", "dev": true, + "license": "ISC", "dependencies": { - "@npmcli/promise-spawn": "^7.0.0", - "lru-cache": "^10.0.1", - "npm-pick-manifest": "^9.0.0", - "proc-log": "^4.0.0", - "promise-inflight": "^1.0.1", + "@npmcli/promise-spawn": "^8.0.0", + "ini": "^5.0.0", + "lru-cache": "^11.2.1", + "npm-pick-manifest": "^11.0.1", + "proc-log": "^5.0.0", "promise-retry": "^2.0.1", "semver": "^7.3.5", - "which": "^4.0.0" + "which": "^5.0.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/@npmcli/git/node_modules/isexe": { @@ -459,15 +592,27 @@ "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", "dev": true, + "license": "ISC", "engines": { "node": ">=16" } }, + "node_modules/@npmcli/git/node_modules/lru-cache": { + "version": "11.2.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.2.tgz", + "integrity": "sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "20 || >=22" + } + }, "node_modules/@npmcli/git/node_modules/which": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", - "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", + "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", "dev": true, + "license": "ISC", "dependencies": { "isexe": "^3.1.1" }, @@ -475,108 +620,66 @@ "node-which": "bin/which.js" }, "engines": { - "node": "^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/@npmcli/installed-package-contents": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@npmcli/installed-package-contents/-/installed-package-contents-2.1.0.tgz", - "integrity": "sha512-c8UuGLeZpm69BryRykLuKRyKFZYJsZSCT4aVY5ds4omyZqJ172ApzgfKJ5eV/r3HgLdUYgFVe54KSFVjKoe27w==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/installed-package-contents/-/installed-package-contents-3.0.0.tgz", + "integrity": "sha512-fkxoPuFGvxyrH+OQzyTkX2LUEamrF4jZSmxjAtPPHHGO0dqsQ8tTKjnIS8SAnPHdk2I03BDtSMR5K/4loKg79Q==", "dev": true, + "license": "ISC", "dependencies": { - "npm-bundled": "^3.0.0", - "npm-normalize-package-bin": "^3.0.0" + "npm-bundled": "^4.0.0", + "npm-normalize-package-bin": "^4.0.0" }, "bin": { "installed-package-contents": "bin/index.js" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/@npmcli/node-gyp": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@npmcli/node-gyp/-/node-gyp-3.0.0.tgz", - "integrity": "sha512-gp8pRXC2oOxu0DUE1/M3bYtb1b3/DbJ5aM113+XJBgfXdussRAsX0YOrOhdd8WvnAR6auDBvJomGAkLKA5ydxA==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/node-gyp/-/node-gyp-4.0.0.tgz", + "integrity": "sha512-+t5DZ6mO/QFh78PByMq1fGSAub/agLJZDRfJRMeOSNCt8s9YVlTjmGpIPwPhvXTGUIJk+WszlT0rQa1W33yzNA==", "dev": true, + "license": "ISC", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/@npmcli/package-json": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/@npmcli/package-json/-/package-json-5.1.1.tgz", - "integrity": "sha512-uTq5j/UqUzbOaOxVy+osfOhpqOiLfUZ0Ut33UbcyyAPJbZcJsf4Mrsyb8r58FoIFlofw0iOFsuCA/oDK14VDJQ==", - "dev": true, - "dependencies": { - "@npmcli/git": "^5.0.0", - "glob": "^10.2.2", - "hosted-git-info": "^7.0.0", - "json-parse-even-better-errors": "^3.0.0", - "normalize-package-data": "^6.0.0", - "proc-log": "^4.0.0", - "semver": "^7.5.3" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "node_modules/@npmcli/package-json/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/@npmcli/package-json/node_modules/glob": { - "version": "10.4.1", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.1.tgz", - "integrity": "sha512-2jelhlq3E4ho74ZyVLN03oKdAZVUa6UDZzFLVH1H7dnoax+y9qyaq8zBkfDIggjniU19z0wU18y16jMB2eyVIw==", - "dev": true, - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "engines": { - "node": ">=16 || 14 >=14.18" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@npmcli/package-json/node_modules/minimatch": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", - "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@npmcli/package-json/-/package-json-7.0.1.tgz", + "integrity": "sha512-956YUeI0YITbk2+KnirCkD19HLzES0habV+Els+dyZaVsaM6VGSiNwnRu6t3CZaqDLz4KXy2zx+0N/Zy6YjlAA==", "dev": true, + "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.1" + "@npmcli/git": "^7.0.0", + "glob": "^11.0.3", + "hosted-git-info": "^9.0.0", + "json-parse-even-better-errors": "^4.0.0", + "proc-log": "^5.0.0", + "semver": "^7.5.3", + "validate-npm-package-license": "^3.0.4" }, "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/@npmcli/promise-spawn": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-7.0.2.tgz", - "integrity": "sha512-xhfYPXoV5Dy4UkY0D+v2KkwvnDfiA/8Mt3sWCGI/hM03NsYIH8ZaG6QzS9x7pje5vHZBZJ2v6VRFVTWACnqcmQ==", + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-8.0.3.tgz", + "integrity": "sha512-Yb00SWaL4F8w+K8YGhQ55+xE4RUNdMHV43WZGsiTM92gS+lC0mGsn7I4hLug7pbao035S6bj3Y3w0cUNGLfmkg==", "dev": true, + "license": "ISC", "dependencies": { - "which": "^4.0.0" + "which": "^5.0.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/@npmcli/promise-spawn/node_modules/isexe": { @@ -584,15 +687,17 @@ "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", "dev": true, + "license": "ISC", "engines": { "node": ">=16" } }, "node_modules/@npmcli/promise-spawn/node_modules/which": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", - "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", + "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", "dev": true, + "license": "ISC", "dependencies": { "isexe": "^3.1.1" }, @@ -600,32 +705,35 @@ "node-which": "bin/which.js" }, "engines": { - "node": "^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/@npmcli/redact": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@npmcli/redact/-/redact-1.1.0.tgz", - "integrity": "sha512-PfnWuOkQgu7gCbnSsAisaX7hKOdZ4wSAhAzH3/ph5dSGau52kCRrMMGbiSQLwyTZpgldkZ49b0brkOr1AzGBHQ==", + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@npmcli/redact/-/redact-3.2.2.tgz", + "integrity": "sha512-7VmYAmk4csGv08QzrDKScdzn11jHPFGyqJW39FyPgPuAp3zIaUmuCo1yxw9aGs+NEJuTGQ9Gwqpt93vtJubucg==", "dev": true, + "license": "ISC", "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/@npmcli/run-script": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-7.0.4.tgz", - "integrity": "sha512-9ApYM/3+rBt9V80aYg6tZfzj3UWdiYyCt7gJUD1VJKvWF5nwKDSICXbYIQbspFTq6TOpbsEtIC0LArB8d9PFmg==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-10.0.0.tgz", + "integrity": "sha512-vaQj4nccJbAslopIvd49pQH2NhUp7G9pY4byUtmwhe37ZZuubGrx0eB9hW2F37uVNRuDDK6byFGXF+7JCuMSZg==", "dev": true, + "license": "ISC", "dependencies": { - "@npmcli/node-gyp": "^3.0.0", - "@npmcli/package-json": "^5.0.0", - "@npmcli/promise-spawn": "^7.0.0", - "node-gyp": "^10.0.0", - "which": "^4.0.0" + "@npmcli/node-gyp": "^4.0.0", + "@npmcli/package-json": "^7.0.0", + "@npmcli/promise-spawn": "^8.0.0", + "node-gyp": "^11.0.0", + "proc-log": "^5.0.0", + "which": "^5.0.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/@npmcli/run-script/node_modules/isexe": { @@ -633,15 +741,17 @@ "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", "dev": true, + "license": "ISC", "engines": { "node": ">=16" } }, "node_modules/@npmcli/run-script/node_modules/which": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", - "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", + "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", "dev": true, + "license": "ISC", "dependencies": { "isexe": "^3.1.1" }, @@ -649,7 +759,7 @@ "node-which": "bin/which.js" }, "engines": { - "node": "^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/@pkgjs/parseargs": { @@ -662,203 +772,290 @@ } }, "node_modules/@sigstore/bundle": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/@sigstore/bundle/-/bundle-2.3.2.tgz", - "integrity": "sha512-wueKWDk70QixNLB363yHc2D2ItTgYiMTdPwK8D9dKQMR3ZQ0c35IxP5xnwQ8cNLoCgCRcHf14kE+CLIvNX1zmA==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@sigstore/bundle/-/bundle-4.0.0.tgz", + "integrity": "sha512-NwCl5Y0V6Di0NexvkTqdoVfmjTaQwoLM236r89KEojGmq/jMls8S+zb7yOwAPdXvbwfKDlP+lmXgAL4vKSQT+A==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@sigstore/protobuf-specs": "^0.3.2" + "@sigstore/protobuf-specs": "^0.5.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/@sigstore/core": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@sigstore/core/-/core-1.1.0.tgz", - "integrity": "sha512-JzBqdVIyqm2FRQCulY6nbQzMpJJpSiJ8XXWMhtOX9eKgaXXpfNOF53lzQEjIydlStnd/eFtuC1dW4VYdD93oRg==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@sigstore/core/-/core-3.0.0.tgz", + "integrity": "sha512-NgbJ+aW9gQl/25+GIEGYcCyi8M+ng2/5X04BMuIgoDfgvp18vDcoNHOQjQsG9418HGNYRxG3vfEXaR1ayD37gg==", "dev": true, + "license": "Apache-2.0", "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/@sigstore/protobuf-specs": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@sigstore/protobuf-specs/-/protobuf-specs-0.3.2.tgz", - "integrity": "sha512-c6B0ehIWxMI8wiS/bj6rHMPqeFvngFV7cDU/MY+B16P9Z3Mp9k8L93eYZ7BYzSickzuqAQqAq0V956b3Ju6mLw==", + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@sigstore/protobuf-specs/-/protobuf-specs-0.5.0.tgz", + "integrity": "sha512-MM8XIwUjN2bwvCg1QvrMtbBmpcSHrkhFSCu1D11NyPvDQ25HEc4oG5/OcQfd/Tlf/OxmKWERDj0zGE23jQaMwA==", "dev": true, + "license": "Apache-2.0", "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/@sigstore/sign": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/@sigstore/sign/-/sign-2.3.2.tgz", - "integrity": "sha512-5Vz5dPVuunIIvC5vBb0APwo7qKA4G9yM48kPWJT+OEERs40md5GoUR1yedwpekWZ4m0Hhw44m6zU+ObsON+iDA==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@sigstore/sign/-/sign-4.0.1.tgz", + "integrity": "sha512-KFNGy01gx9Y3IBPG/CergxR9RZpN43N+lt3EozEfeoyqm8vEiLxwRl3ZO5sPx3Obv1ix/p7FWOlPc2Jgwfp9PA==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@sigstore/bundle": "^2.3.2", - "@sigstore/core": "^1.0.0", - "@sigstore/protobuf-specs": "^0.3.2", - "make-fetch-happen": "^13.0.1", - "proc-log": "^4.2.0", + "@sigstore/bundle": "^4.0.0", + "@sigstore/core": "^3.0.0", + "@sigstore/protobuf-specs": "^0.5.0", + "make-fetch-happen": "^15.0.2", + "proc-log": "^5.0.0", "promise-retry": "^2.0.1" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@sigstore/sign/node_modules/@npmcli/agent": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-4.0.0.tgz", + "integrity": "sha512-kAQTcEN9E8ERLVg5AsGwLNoFb+oEG6engbqAU2P43gD4JEIkNGMHdVQ096FsOAAYpZPB0RSt0zgInKIAS1l5QA==", + "dev": true, + "license": "ISC", + "dependencies": { + "agent-base": "^7.1.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.1", + "lru-cache": "^11.2.1", + "socks-proxy-agent": "^8.0.3" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@sigstore/sign/node_modules/lru-cache": { + "version": "11.2.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.2.tgz", + "integrity": "sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@sigstore/sign/node_modules/make-fetch-happen": { + "version": "15.0.2", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-15.0.2.tgz", + "integrity": "sha512-sI1NY4lWlXBAfjmCtVWIIpBypbBdhHtcjnwnv+gtCnsaOffyFil3aidszGC8hgzJe+fT1qix05sWxmD/Bmf/oQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/agent": "^4.0.0", + "cacache": "^20.0.1", + "http-cache-semantics": "^4.1.1", + "minipass": "^7.0.2", + "minipass-fetch": "^4.0.0", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^1.0.0", + "proc-log": "^5.0.0", + "promise-retry": "^2.0.1", + "ssri": "^12.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/@sigstore/tuf": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/@sigstore/tuf/-/tuf-2.3.4.tgz", - "integrity": "sha512-44vtsveTPUpqhm9NCrbU8CWLe3Vck2HO1PNLw7RIajbB7xhtn5RBPm1VNSCMwqGYHhDsBJG8gDF0q4lgydsJvw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@sigstore/tuf/-/tuf-4.0.0.tgz", + "integrity": "sha512-0QFuWDHOQmz7t66gfpfNO6aEjoFrdhkJaej/AOqb4kqWZVbPWFZifXZzkxyQBB1OwTbkhdT3LNpMFxwkTvf+2w==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@sigstore/protobuf-specs": "^0.3.2", - "tuf-js": "^2.2.1" + "@sigstore/protobuf-specs": "^0.5.0", + "tuf-js": "^4.0.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/@sigstore/verify": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@sigstore/verify/-/verify-1.2.1.tgz", - "integrity": "sha512-8iKx79/F73DKbGfRf7+t4dqrc0bRr0thdPrxAtCKWRm/F0tG71i6O1rvlnScncJLLBZHn3h8M3c1BSUAb9yu8g==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@sigstore/verify/-/verify-3.0.0.tgz", + "integrity": "sha512-moXtHH33AobOhTZF8xcX1MpOFqdvfCk7v6+teJL8zymBiDXwEsQH6XG9HGx2VIxnJZNm4cNSzflTLDnQLmIdmw==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@sigstore/bundle": "^2.3.2", - "@sigstore/core": "^1.1.0", - "@sigstore/protobuf-specs": "^0.3.2" + "@sigstore/bundle": "^4.0.0", + "@sigstore/core": "^3.0.0", + "@sigstore/protobuf-specs": "^0.5.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/@tapjs/after": { - "version": "1.1.24", - "resolved": "https://registry.npmjs.org/@tapjs/after/-/after-1.1.24.tgz", - "integrity": "sha512-Qys3CtftkfHGC7thDGm9TBzRCBLAoJKrXufF1zQxI1oNUjclWZP/s8CtHH0mwUTISOTehmBLV3wPPHSslD67Ng==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@tapjs/after/-/after-3.0.2.tgz", + "integrity": "sha512-Xb0bqJWXfp6VVSx1T96lNJM67v2XxJCG3o7mH77weB+RzwAuf0uzGYy/hxP+nUAWh9yH+lHzuHclL+DR8Zlu3Q==", "dev": true, + "license": "BlueOak-1.0.0", "dependencies": { "is-actual-promise": "^1.0.1" }, "engines": { - "node": "16 >=16.17.0 || 18 >= 18.6.0 || >=20" + "node": "20 || >=22" }, "peerDependencies": { - "@tapjs/core": "2.0.1" + "@tapjs/core": "4.0.2" } }, "node_modules/@tapjs/after-each": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@tapjs/after-each/-/after-each-2.0.1.tgz", - "integrity": "sha512-3JXIJ4g9LPjyXmn/1VuIMC0vh7uBgUpQPksjffxv0rL8wq4C8lvmqt8Qu/fVImJucqzA+WrRqVG1b2Ab0ocDOw==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@tapjs/after-each/-/after-each-4.0.2.tgz", + "integrity": "sha512-OQEENy55qtJ7WaMnIQvf0YaV4/YjI2B9+ezn679Vrptg/xMbaG5fSJz/Z5BFroh05HiWRo+MGu66q7Lb00kDJg==", "dev": true, + "license": "BlueOak-1.0.0", "dependencies": { "function-loop": "^4.0.0" }, "engines": { - "node": "16 >=16.17.0 || 18 >= 18.6.0 || >=20" + "node": "20 || >=22" }, "peerDependencies": { - "@tapjs/core": "2.0.1" + "@tapjs/core": "4.0.2" } }, "node_modules/@tapjs/asserts": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@tapjs/asserts/-/asserts-2.0.1.tgz", - "integrity": "sha512-v2xYDLUwMGt8pzoY5LIjDCaw2NM+G01NW4pC3RcpsZLZbzQv1x/phi2RAX0ixI0nCmZZybqRygFKuMcJamS+gg==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@tapjs/asserts/-/asserts-4.0.2.tgz", + "integrity": "sha512-XeAYvYMu61/Gc9Dpn+0QZCjUTbbQE7DckiZNhQNMuMwAXUDEZR/TbxJNysIdMWq2ag75TQQ6ylCUJovy7HPDlg==", "dev": true, + "license": "BlueOak-1.0.0", "dependencies": { - "@tapjs/stack": "2.0.1", + "@tapjs/stack": "4.0.1", "is-actual-promise": "^1.0.1", - "tcompare": "7.0.1", + "tcompare": "9.0.1", "trivial-deferred": "^2.0.0" }, "engines": { - "node": "16 >=16.17.0 || 18 >= 18.6.0 || >=20" + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" }, "peerDependencies": { - "@tapjs/core": "2.0.1" + "@tapjs/core": "4.0.2" + } + }, + "node_modules/@tapjs/asserts/node_modules/diff": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.2.tgz", + "integrity": "sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" } }, "node_modules/@tapjs/asserts/node_modules/tcompare": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/tcompare/-/tcompare-7.0.1.tgz", - "integrity": "sha512-JN5s7hgmg/Ya5HxZqCnywT+XiOGRFcJRgYhtMyt/1m+h0yWpWwApO7HIM8Bpwyno9hI151ljjp5eAPCHhIGbpQ==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/tcompare/-/tcompare-9.0.1.tgz", + "integrity": "sha512-P9i7K3RnKWb3mH0AmDCChVd7FP0LEo4VSBJMhjKB+Rg+Tvq09ytnys8ygsx5ve1di2fI4IW+HhjhIXoeiRVogw==", "dev": true, + "license": "BlueOak-1.0.0", "dependencies": { - "diff": "^5.2.0", + "diff": "^8.0.2", "react-element-to-jsx-string": "^15.0.0" }, "engines": { - "node": "16 >=16.17.0 || 18 >= 18.6.0 || >=20" + "node": "20 || >=22" } }, "node_modules/@tapjs/before": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@tapjs/before/-/before-2.0.1.tgz", - "integrity": "sha512-GgnlWPm2PbuyYuG4gkkO2KAvT/BbGnpKs60U4XzPSJ2w73Qc/IYWP0Kz6qfCWongpiLteoco67M89ujUQApYJw==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@tapjs/before/-/before-4.0.2.tgz", + "integrity": "sha512-ODi0rXOqCWZLS1j6fJ2iyqaXy6B/y75x8Y940hRAe0DfPPf48IgZY84+GfWWVIkRqmbYZS/F0rwcegPHxI/xHg==", "dev": true, + "license": "BlueOak-1.0.0", "dependencies": { "is-actual-promise": "^1.0.1" }, "engines": { - "node": "16 >=16.17.0 || 18 >= 18.6.0 || >=20" + "node": "20 || >=22" }, "peerDependencies": { - "@tapjs/core": "2.0.1" + "@tapjs/core": "4.0.2" } }, "node_modules/@tapjs/before-each": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@tapjs/before-each/-/before-each-2.0.1.tgz", - "integrity": "sha512-gG1nYkvCHtWwhkueulO475KczdQZ3vBRgdkta/Qi42ZjZo6SNhYVjNc/+LRGV5vZoESrvgSd+JrDRGufd+j43w==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@tapjs/before-each/-/before-each-4.0.2.tgz", + "integrity": "sha512-CXNWJ/pvqu3DwNjgnOX9zfmxYz7OvxQ4w7X/1uyFUblWarYYOUqtLsF/FrBHn3rqOowNtP5KJAzY9Wx5UT3r+g==", "dev": true, + "license": "BlueOak-1.0.0", "dependencies": { "function-loop": "^4.0.0" }, "engines": { - "node": "16 >=16.17.0 || 18 >= 18.6.0 || >=20" + "node": "20 || >=22" }, "peerDependencies": { - "@tapjs/core": "2.0.1" + "@tapjs/core": "4.0.2" + } + }, + "node_modules/@tapjs/chdir": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@tapjs/chdir/-/chdir-3.0.2.tgz", + "integrity": "sha512-KYDxkkzGeEhAT33VDWqIx8FRLk+cOzVsj/E0JBRd45n18EHT7C/wE/Nqhu+843nVhABp/c5BZMb+Rib/fCQV1g==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + }, + "peerDependencies": { + "@tapjs/core": "4.0.2" } }, "node_modules/@tapjs/config": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@tapjs/config/-/config-3.0.1.tgz", - "integrity": "sha512-gAYFzErdSuPQ3afW6iRR99hiJmRLU+x9T+NE89z9UM45iPxglWLrRv1PFfh3tmtX6rpzwD5RY4/FVPcP2+/1LQ==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@tapjs/config/-/config-5.0.2.tgz", + "integrity": "sha512-VfvYwcRGC3fbBAuRZs8VheA4YTa1CaJlYonHM2YM1Uu+UGeLCmvxJjIOBauhtp+F/QczUsJxqOjDy9z9ny1UYQ==", "dev": true, + "license": "BlueOak-1.0.0", "dependencies": { - "@tapjs/core": "2.0.1", - "@tapjs/test": "2.0.1", - "chalk": "^5.2.0", - "jackspeak": "^3.1.2", - "polite-json": "^4.0.1", - "tap-yaml": "2.2.2", - "walk-up-path": "^3.0.1" + "@tapjs/core": "4.0.2", + "@tapjs/test": "4.0.2", + "chalk": "^5.6.2", + "jackspeak": "^4.0.1", + "polite-json": "^5.0.0", + "tap-yaml": "4.0.1", + "walk-up-path": "^4.0.0" }, "engines": { - "node": "16 >=16.17.0 || 18 >= 18.6.0 || >=20" + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" }, "peerDependencies": { - "@tapjs/core": "2.0.1", - "@tapjs/test": "2.0.1" + "@tapjs/core": "4.0.2", + "@tapjs/test": "4.0.2" } }, "node_modules/@tapjs/config/node_modules/chalk": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", - "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", "dev": true, + "license": "MIT", "engines": { "node": "^12.17.0 || ^14.13 || >=16.0.0" }, @@ -866,153 +1063,208 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/@tapjs/config/node_modules/jackspeak": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.1.1.tgz", + "integrity": "sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/@tapjs/core": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@tapjs/core/-/core-2.0.1.tgz", - "integrity": "sha512-q+8d+ohw5kudktIqgP5ETBcPWAPip+kMIxs2eL2G3dV+7Gc8WrH43cCPrbSGPRITIOSIDPrtpQZEcZwQNqDdQw==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@tapjs/core/-/core-4.0.2.tgz", + "integrity": "sha512-kzM90qsqHAJOTUMVjB3G26c3Ka4/HFg253lSt3pxBFesIHneIZe6Fre1NEWnHZPLmmzBO6HRRffc9zDxXcaeRw==", "dev": true, + "license": "BlueOak-1.0.0", "dependencies": { - "@tapjs/processinfo": "^3.1.7", - "@tapjs/stack": "2.0.1", - "@tapjs/test": "2.0.1", + "@tapjs/processinfo": "^3.1.8", + "@tapjs/stack": "4.0.1", + "@tapjs/test": "4.0.2", "async-hook-domain": "^4.0.1", - "diff": "^5.2.0", + "diff": "^8.0.2", "is-actual-promise": "^1.0.1", "minipass": "^7.0.4", "signal-exit": "4.1", - "tap-parser": "16.0.1", - "tap-yaml": "2.2.2", - "tcompare": "7.0.1", + "tap-parser": "18.0.1", + "tap-yaml": "4.0.1", + "tcompare": "9.0.1", "trivial-deferred": "^2.0.0" }, "engines": { - "node": "16 >=16.17.0 || 18 >= 18.6.0 || >=20" + "node": "20 || >=22" + } + }, + "node_modules/@tapjs/core/node_modules/diff": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.2.tgz", + "integrity": "sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" } }, "node_modules/@tapjs/core/node_modules/tcompare": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/tcompare/-/tcompare-7.0.1.tgz", - "integrity": "sha512-JN5s7hgmg/Ya5HxZqCnywT+XiOGRFcJRgYhtMyt/1m+h0yWpWwApO7HIM8Bpwyno9hI151ljjp5eAPCHhIGbpQ==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/tcompare/-/tcompare-9.0.1.tgz", + "integrity": "sha512-P9i7K3RnKWb3mH0AmDCChVd7FP0LEo4VSBJMhjKB+Rg+Tvq09ytnys8ygsx5ve1di2fI4IW+HhjhIXoeiRVogw==", "dev": true, + "license": "BlueOak-1.0.0", "dependencies": { - "diff": "^5.2.0", + "diff": "^8.0.2", "react-element-to-jsx-string": "^15.0.0" }, "engines": { - "node": "16 >=16.17.0 || 18 >= 18.6.0 || >=20" + "node": "20 || >=22" } }, "node_modules/@tapjs/error-serdes": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@tapjs/error-serdes/-/error-serdes-2.0.1.tgz", - "integrity": "sha512-P+M4rtcfkDsUveKKmoRNF+07xpbPnRY5KrstIUOnyn483clQ7BJhsnWr162yYNCsyOj4zEfZmAJI1f8Bi7h/ZA==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@tapjs/error-serdes/-/error-serdes-4.0.1.tgz", + "integrity": "sha512-8GiOXbgGIRBcSGnPXYuiboy0xJQDMP2OcILnghHX/jzJKi2l9mxX6FTonOWj/0qsf5Ji5Z4/DIKRcYINIxaejg==", "dev": true, + "license": "BlueOak-1.0.0", "dependencies": { "minipass": "^7.0.4" }, "engines": { - "node": "16 >=16.17.0 || 18 >= 18.6.0 || >=20" + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/@tapjs/filter": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@tapjs/filter/-/filter-2.0.1.tgz", - "integrity": "sha512-muKEeXK7Tz6VR4hjXfT2qXPvjYES575mtiRerjHf+8qP8D7MvmC8qDZJjzFdo1nZHKhF8snvFosIVuI1BAhvsw==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@tapjs/filter/-/filter-4.0.2.tgz", + "integrity": "sha512-Pfi1u1naoUL4G4AoJOUEUQauaZeF4gGk8MZ8It2ht60gopYsxy9sSmIKlKPvZm13f110pZxJfO+hogappAmDfw==", "dev": true, + "license": "BlueOak-1.0.0", "engines": { - "node": "16 >=16.17.0 || 18 >= 18.6.0 || >=20" + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" }, "peerDependencies": { - "@tapjs/core": "2.0.1" + "@tapjs/core": "4.0.2" } }, "node_modules/@tapjs/fixture": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@tapjs/fixture/-/fixture-2.0.1.tgz", - "integrity": "sha512-MLgEwsBlCD69iUbZcnKBehP2js5cV4p5GrFoOKSudMuH2DQJInaF/g2bkijue61cVZwPj/MRPCqAlkwA94epjg==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@tapjs/fixture/-/fixture-4.0.2.tgz", + "integrity": "sha512-h161PA9ngT3zG6iLybAUlrSWx31OFx7Av1aR4FHvui9IkseXaF0x6gxS0MMaXwdyRYrD7lFvmU7SRTDlvvdeBA==", "dev": true, + "license": "BlueOak-1.0.0", "dependencies": { "mkdirp": "^3.0.0", - "rimraf": "^5.0.5" + "rimraf": "^6.0.0" }, "engines": { - "node": "16 >=16.17.0 || 18 >= 18.6.0 || >=20" + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" }, "peerDependencies": { - "@tapjs/core": "2.0.1" + "@tapjs/core": "4.0.2" + } + }, + "node_modules/@tapjs/fixture/node_modules/rimraf": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-6.0.1.tgz", + "integrity": "sha512-9dkvaxAsk/xNXSJzMgFqqMCuFgt2+KsOFek3TMLfo8NCPfWpBmqwyNn5Y+NX56QUYfCtsyhF3ayiboEoUmJk/A==", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^11.0.0", + "package-json-from-dist": "^1.0.0" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/@tapjs/intercept": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@tapjs/intercept/-/intercept-2.0.1.tgz", - "integrity": "sha512-BZgXE3zCAbv4lfbph1r85gihtI3kXltHlFQ8Bf3Yy9fx27DKQlBvXnD7T69ke8kQLRzhz+wTMcR/mcQjo1fa7w==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@tapjs/intercept/-/intercept-4.0.2.tgz", + "integrity": "sha512-rlZcO/Yf9vU8ypfQEO0c/QUCcGCr0IMJltg53805ILuNhTDR7x0vlJSvJ7fLsuoezkUwXlWdlED6ZqIID7RokA==", "dev": true, + "license": "BlueOak-1.0.0", "dependencies": { - "@tapjs/after": "1.1.24", - "@tapjs/stack": "2.0.1" + "@tapjs/after": "3.0.2", + "@tapjs/stack": "4.0.1" }, "engines": { - "node": "16 >=16.17.0 || 18 >= 18.6.0 || >=20" + "node": "20 || >=22" }, "peerDependencies": { - "@tapjs/core": "2.0.1" + "@tapjs/core": "4.0.2" } }, "node_modules/@tapjs/mock": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@tapjs/mock/-/mock-2.0.1.tgz", - "integrity": "sha512-i1vkwNgO7uEuQW3+hTuE2L64aC9xk0cC3PtC6DZKqyApk2IstNgoIS38nfsI6v2kvEgZNuWlsNcRAYNDOIEhzA==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@tapjs/mock/-/mock-4.0.2.tgz", + "integrity": "sha512-48o3xz3Xl83Ei25KOR5aWytbCYUl33GQAoQm4khFpTyI/v3fjyR9jDgFLAT7IWix8tRrrkMf+PynmZkWVQ4oKA==", "dev": true, + "license": "BlueOak-1.0.0", "dependencies": { - "@tapjs/after": "1.1.24", - "@tapjs/stack": "2.0.1", - "resolve-import": "^1.4.5", - "walk-up-path": "^3.0.1" + "@tapjs/after": "3.0.2", + "@tapjs/stack": "4.0.1", + "resolve-import": "^2.0.0", + "walk-up-path": "^4.0.0" }, "engines": { - "node": "16 >=16.17.0 || 18 >= 18.6.0 || >=20" + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" }, "peerDependencies": { - "@tapjs/core": "2.0.1" + "@tapjs/core": "4.0.2" } }, "node_modules/@tapjs/node-serialize": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@tapjs/node-serialize/-/node-serialize-2.0.1.tgz", - "integrity": "sha512-1GtHDa7AXpk8y08llIPfUKRTDNsq+BhXxz7wiIfVEAOEB09kGyfpWteOg+cmvb+aHU1Ays3z+medXTIBm0D5Kg==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@tapjs/node-serialize/-/node-serialize-4.0.2.tgz", + "integrity": "sha512-oparVnZqbwF7O7IQn4nfpnsddtmlkWPB033uzZbvSW6v0U6T8DBENgKAfzJ2sNQH3f2Xaa3dqK+N5brwQa3P2g==", "dev": true, + "license": "BlueOak-1.0.0", "dependencies": { - "@tapjs/error-serdes": "2.0.1", - "@tapjs/stack": "2.0.1", - "tap-parser": "16.0.1" + "@tapjs/error-serdes": "4.0.1", + "@tapjs/stack": "4.0.1", + "tap-parser": "18.0.1" }, "engines": { - "node": "16 >=16.17.0 || 18 >= 18.6.0 || >=20" + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" }, "peerDependencies": { - "@tapjs/core": "2.0.1" + "@tapjs/core": "4.0.2" } }, "node_modules/@tapjs/processinfo": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/@tapjs/processinfo/-/processinfo-3.1.7.tgz", - "integrity": "sha512-SI5RJQ5HnUKEWnHSAF6hOm6XPdnjZ+CJzIaVHdFebed8iDAPTqb+IwMVu9yq9+VQ7FRsMMlgLL2SW4rss2iJbQ==", + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/@tapjs/processinfo/-/processinfo-3.1.8.tgz", + "integrity": "sha512-FIriEB+qqArPhmVYc1PZwRHD99myRdl7C9Oe/uts04Q2LOxQ5MEmqP9XOP8vVYzpDOYwmL8OmL6eOYt9eZlQKQ==", "dev": true, + "license": "ISC", "dependencies": { "pirates": "^4.0.5", "process-on-spawn": "^1.0.0", @@ -1024,40 +1276,42 @@ } }, "node_modules/@tapjs/reporter": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@tapjs/reporter/-/reporter-2.0.1.tgz", - "integrity": "sha512-fCdl4vg8vnlqIYtTQ9dc3zOqeXrA5QbATbT4dsPIiPuCM3gvKTbntaNBeaWWZkPx697Dj+b8TIxT/xhNMNv7jQ==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@tapjs/reporter/-/reporter-4.0.3.tgz", + "integrity": "sha512-Mdx3+C3f4q3llawja5V2RVIfFzr1KhEm57i9xUJTgM5kHcPjrMw14aQUCwYxLSYSd5esOubnnV5lD9rxQb+tyQ==", "dev": true, + "license": "BlueOak-1.0.0", "dependencies": { - "@tapjs/config": "3.0.1", - "@tapjs/stack": "2.0.1", - "chalk": "^5.2.0", - "ink": "^4.4.1", + "@tapjs/config": "5.0.2", + "@tapjs/stack": "4.0.1", + "chalk": "^5.6.2", + "ink": "^5.2.1", "minipass": "^7.0.4", "ms": "^2.1.3", "patch-console": "^2.0.0", "prismjs-terminal": "^1.2.3", "react": "^18.2.0", "string-length": "^6.0.0", - "tap-parser": "16.0.1", - "tap-yaml": "2.2.2", - "tcompare": "7.0.1" + "tap-parser": "18.0.1", + "tap-yaml": "4.0.1", + "tcompare": "9.0.1" }, "engines": { - "node": "16 >=16.17.0 || 18 >= 18.6.0 || >=20" + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" }, "peerDependencies": { - "@tapjs/core": "2.0.1" + "@tapjs/core": "4.0.2" } }, "node_modules/@tapjs/reporter/node_modules/chalk": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", - "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", "dev": true, + "license": "MIT", "engines": { "node": "^12.17.0 || ^14.13 || >=16.0.0" }, @@ -1065,85 +1319,84 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@tapjs/reporter/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true + "node_modules/@tapjs/reporter/node_modules/diff": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.2.tgz", + "integrity": "sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } }, "node_modules/@tapjs/reporter/node_modules/tcompare": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/tcompare/-/tcompare-7.0.1.tgz", - "integrity": "sha512-JN5s7hgmg/Ya5HxZqCnywT+XiOGRFcJRgYhtMyt/1m+h0yWpWwApO7HIM8Bpwyno9hI151ljjp5eAPCHhIGbpQ==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/tcompare/-/tcompare-9.0.1.tgz", + "integrity": "sha512-P9i7K3RnKWb3mH0AmDCChVd7FP0LEo4VSBJMhjKB+Rg+Tvq09ytnys8ygsx5ve1di2fI4IW+HhjhIXoeiRVogw==", "dev": true, + "license": "BlueOak-1.0.0", "dependencies": { - "diff": "^5.2.0", + "diff": "^8.0.2", "react-element-to-jsx-string": "^15.0.0" }, "engines": { - "node": "16 >=16.17.0 || 18 >= 18.6.0 || >=20" + "node": "20 || >=22" } }, "node_modules/@tapjs/run": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@tapjs/run/-/run-2.0.2.tgz", - "integrity": "sha512-2hPGlabqbLb3hh4BHHvwE8R9a9OiWumkCkHw5QQUZurDsVOpB94FfteqW9mktTVjZJnN0go+sN3GN2jZUaPWGQ==", - "dev": true, - "dependencies": { - "@tapjs/after": "1.1.24", - "@tapjs/before": "2.0.1", - "@tapjs/config": "3.0.1", - "@tapjs/processinfo": "^3.1.7", - "@tapjs/reporter": "2.0.1", - "@tapjs/spawn": "2.0.1", - "@tapjs/stdin": "2.0.1", - "@tapjs/test": "2.0.1", - "c8": "^9.1.0", - "chalk": "^5.3.0", - "chokidar": "^3.6.0", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@tapjs/run/-/run-4.0.3.tgz", + "integrity": "sha512-kkoMk5OJPgxigZZwu47ix2Zo8U64t8YiFiz+Iec5HrSW1BNgQyhurfMbc8uGjBHh3OhvJBaWhWuzctn0ROJrEQ==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@tapjs/after": "3.0.2", + "@tapjs/before": "4.0.2", + "@tapjs/config": "5.0.2", + "@tapjs/processinfo": "^3.1.8", + "@tapjs/reporter": "4.0.3", + "@tapjs/spawn": "4.0.2", + "@tapjs/stdin": "4.0.2", + "@tapjs/test": "4.0.2", + "c8": "^10.1.3", + "chalk": "^5.6.2", + "chokidar": "^4.0.2", "foreground-child": "^3.1.1", - "glob": "^10.3.16", + "glob": "^11.0.0", "minipass": "^7.0.4", "mkdirp": "^3.0.1", "opener": "^1.5.2", - "pacote": "^17.0.6", - "resolve-import": "^1.4.5", - "rimraf": "^5.0.5", - "semver": "^7.6.0", + "pacote": "^21.0.3", + "path-scurry": "^2.0.0", + "resolve-import": "^2.0.0", + "rimraf": "^6.0.0", + "semver": "^7.7.2", "signal-exit": "^4.1.0", - "tap-parser": "16.0.1", - "tap-yaml": "2.2.2", - "tcompare": "7.0.1", + "tap-parser": "18.0.1", + "tap-yaml": "4.0.1", + "tcompare": "9.0.1", "trivial-deferred": "^2.0.0", - "which": "^4.0.0" + "which": "^5.0.0" }, "bin": { "tap-run": "dist/esm/index.js" }, "engines": { - "node": "16 >=16.17.0 || 18 >= 18.6.0 || >=20" + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" }, "peerDependencies": { - "@tapjs/core": "2.0.1" - } - }, - "node_modules/@tapjs/run/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" + "@tapjs/core": "4.0.2" } }, "node_modules/@tapjs/run/node_modules/chalk": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", - "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", "dev": true, + "license": "MIT", "engines": { "node": "^12.17.0 || ^14.13 || >=16.0.0" }, @@ -1151,26 +1404,30 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@tapjs/run/node_modules/glob": { - "version": "10.4.1", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.1.tgz", - "integrity": "sha512-2jelhlq3E4ho74ZyVLN03oKdAZVUa6UDZzFLVH1H7dnoax+y9qyaq8zBkfDIggjniU19z0wU18y16jMB2eyVIw==", + "node_modules/@tapjs/run/node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", "dev": true, + "license": "MIT", "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" + "readdirp": "^4.0.1" }, "engines": { - "node": ">=16 || 14 >=14.18" + "node": ">= 14.16.0" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@tapjs/run/node_modules/diff": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.2.tgz", + "integrity": "sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" } }, "node_modules/@tapjs/run/node_modules/isexe": { @@ -1178,43 +1435,92 @@ "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", "dev": true, + "license": "ISC", "engines": { "node": ">=16" } }, - "node_modules/@tapjs/run/node_modules/minimatch": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", - "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", + "node_modules/@tapjs/run/node_modules/lru-cache": { + "version": "11.2.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.2.tgz", + "integrity": "sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==", "dev": true, + "license": "ISC", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@tapjs/run/node_modules/path-scurry": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", + "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==", + "dev": true, + "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^2.0.1" + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@tapjs/run/node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@tapjs/run/node_modules/rimraf": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-6.0.1.tgz", + "integrity": "sha512-9dkvaxAsk/xNXSJzMgFqqMCuFgt2+KsOFek3TMLfo8NCPfWpBmqwyNn5Y+NX56QUYfCtsyhF3ayiboEoUmJk/A==", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^11.0.0", + "package-json-from-dist": "^1.0.0" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/@tapjs/run/node_modules/tcompare": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/tcompare/-/tcompare-7.0.1.tgz", - "integrity": "sha512-JN5s7hgmg/Ya5HxZqCnywT+XiOGRFcJRgYhtMyt/1m+h0yWpWwApO7HIM8Bpwyno9hI151ljjp5eAPCHhIGbpQ==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/tcompare/-/tcompare-9.0.1.tgz", + "integrity": "sha512-P9i7K3RnKWb3mH0AmDCChVd7FP0LEo4VSBJMhjKB+Rg+Tvq09ytnys8ygsx5ve1di2fI4IW+HhjhIXoeiRVogw==", "dev": true, + "license": "BlueOak-1.0.0", "dependencies": { - "diff": "^5.2.0", + "diff": "^8.0.2", "react-element-to-jsx-string": "^15.0.0" }, "engines": { - "node": "16 >=16.17.0 || 18 >= 18.6.0 || >=20" + "node": "20 || >=22" } }, "node_modules/@tapjs/run/node_modules/which": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", - "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", + "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", "dev": true, + "license": "ISC", "dependencies": { "isexe": "^3.1.1" }, @@ -1222,254 +1528,272 @@ "node-which": "bin/which.js" }, "engines": { - "node": "^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/@tapjs/snapshot": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@tapjs/snapshot/-/snapshot-2.0.1.tgz", - "integrity": "sha512-ZnbCxL+9fiJ38tec6wvRtRBZz9ChRUq0Bov7dltdZMNkXqudKyB+Zzbg25bqDEIgcczyp6A9hOwTX6VybDGqpg==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@tapjs/snapshot/-/snapshot-4.0.2.tgz", + "integrity": "sha512-PWlWNEH+4x0oN8nemk+2rk3jub2L/7c6A383SD15GadGOT4hYvckqY2mCZarMAoV5xErFZOglGTD9do83TWMPQ==", "dev": true, + "license": "BlueOak-1.0.0", "dependencies": { "is-actual-promise": "^1.0.1", - "tcompare": "7.0.1", + "tcompare": "9.0.1", "trivial-deferred": "^2.0.0" }, "engines": { - "node": "16 >=16.17.0 || 18 >= 18.6.0 || >=20" + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" }, "peerDependencies": { - "@tapjs/core": "2.0.1" + "@tapjs/core": "4.0.2" + } + }, + "node_modules/@tapjs/snapshot/node_modules/diff": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.2.tgz", + "integrity": "sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" } }, "node_modules/@tapjs/snapshot/node_modules/tcompare": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/tcompare/-/tcompare-7.0.1.tgz", - "integrity": "sha512-JN5s7hgmg/Ya5HxZqCnywT+XiOGRFcJRgYhtMyt/1m+h0yWpWwApO7HIM8Bpwyno9hI151ljjp5eAPCHhIGbpQ==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/tcompare/-/tcompare-9.0.1.tgz", + "integrity": "sha512-P9i7K3RnKWb3mH0AmDCChVd7FP0LEo4VSBJMhjKB+Rg+Tvq09ytnys8ygsx5ve1di2fI4IW+HhjhIXoeiRVogw==", "dev": true, + "license": "BlueOak-1.0.0", "dependencies": { - "diff": "^5.2.0", + "diff": "^8.0.2", "react-element-to-jsx-string": "^15.0.0" }, "engines": { - "node": "16 >=16.17.0 || 18 >= 18.6.0 || >=20" + "node": "20 || >=22" } }, "node_modules/@tapjs/spawn": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@tapjs/spawn/-/spawn-2.0.1.tgz", - "integrity": "sha512-3VaQKJjHV5frMZj3Ef+QlJyB6b7VsGMil223zAEz8Ttgy2hDYtcb29nvsLPUcowFyOUrsydnXEnHgpR79wEPOA==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@tapjs/spawn/-/spawn-4.0.2.tgz", + "integrity": "sha512-CNOrZFXh+mm2iul/cGRgVA4HMv9GxmtbgQSICMjRrj8VLDzMboW862P+wEvfYrHeXdCGAGWKqTIcsX5pXQVQRA==", "dev": true, + "license": "BlueOak-1.0.0", "engines": { - "node": "16 >=16.17.0 || 18 >= 18.6.0 || >=20" + "node": "20 || >=22" }, "peerDependencies": { - "@tapjs/core": "2.0.1" + "@tapjs/core": "4.0.2" } }, "node_modules/@tapjs/stack": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@tapjs/stack/-/stack-2.0.1.tgz", - "integrity": "sha512-3rKbZkRkLeJl9ilV/6b80YfI4C4+OYf7iEz5/d0MIVhmVvxv0ttIy5JnZutAc4Gy9eRp5Ne5UTAIFOVY5k36cg==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@tapjs/stack/-/stack-4.0.1.tgz", + "integrity": "sha512-Rbyz4XMuZWNxCs+/j0c5idFz4MKBo7uSaNvk6R7Al9jQJzk7Lv0WC2lWW0CV+7t/TUynTFxEwAaY5pIM752WQg==", "dev": true, + "license": "BlueOak-1.0.0", "engines": { - "node": "16 >=16.17.0 || 18 >= 18.6.0 || >=20" + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/@tapjs/stdin": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@tapjs/stdin/-/stdin-2.0.1.tgz", - "integrity": "sha512-5Oe13Fzpnt9seAi8h3bsMxtJp8S+DQI6ncBD9JBcS91XKLbqyKrb1bNzeXQN2PrHBs6Atw8cOzFZh0TjL+bIaA==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@tapjs/stdin/-/stdin-4.0.2.tgz", + "integrity": "sha512-6e/jQ0I9G6DC9m6Yj3jC6sNAmiwvrPVni3iMpJn3GICs0dROyx1m9wnMBK5wgKNgN2AXfvEOtf5Cby124eHeJQ==", "dev": true, + "license": "BlueOak-1.0.0", "engines": { - "node": "16 >=16.17.0 || 18 >= 18.6.0 || >=20" + "node": "20 || >=22" }, "peerDependencies": { - "@tapjs/core": "2.0.1" + "@tapjs/core": "4.0.2" } }, "node_modules/@tapjs/test": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@tapjs/test/-/test-2.0.1.tgz", - "integrity": "sha512-PKazf7r4+bLFATML2f/h8glGcSirXmzXUYlhFuxb4xHoOhHojyKgo1p8kSj+Ksxb3hVSCQlvyXgM8QYYaoMwog==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@tapjs/test/-/test-4.0.2.tgz", + "integrity": "sha512-J8WOSesfqp6/P5UbChDI5xzREQ96787ZFHLliPva4oi5XevG1TWtfSL47HtbQUtKvWSC7YIWW3CQhcBHRGk6Vg==", "dev": true, + "license": "BlueOak-1.0.0", "dependencies": { "@isaacs/ts-node-temp-fork-for-pr-2009": "^10.9.7", - "@tapjs/after": "1.1.24", - "@tapjs/after-each": "2.0.1", - "@tapjs/asserts": "2.0.1", - "@tapjs/before": "2.0.1", - "@tapjs/before-each": "2.0.1", - "@tapjs/filter": "2.0.1", - "@tapjs/fixture": "2.0.1", - "@tapjs/intercept": "2.0.1", - "@tapjs/mock": "2.0.1", - "@tapjs/node-serialize": "2.0.1", - "@tapjs/snapshot": "2.0.1", - "@tapjs/spawn": "2.0.1", - "@tapjs/stdin": "2.0.1", - "@tapjs/typescript": "1.4.6", - "@tapjs/worker": "2.0.1", - "glob": "^10.3.16", - "jackspeak": "^3.1.2", + "@tapjs/after": "3.0.2", + "@tapjs/after-each": "4.0.2", + "@tapjs/asserts": "4.0.2", + "@tapjs/before": "4.0.2", + "@tapjs/before-each": "4.0.2", + "@tapjs/chdir": "3.0.2", + "@tapjs/filter": "4.0.2", + "@tapjs/fixture": "4.0.2", + "@tapjs/intercept": "4.0.2", + "@tapjs/mock": "4.0.2", + "@tapjs/node-serialize": "4.0.2", + "@tapjs/snapshot": "4.0.2", + "@tapjs/spawn": "4.0.2", + "@tapjs/stdin": "4.0.2", + "@tapjs/typescript": "3.1.1", + "@tapjs/worker": "4.0.2", + "glob": "^11.0.3", + "jackspeak": "^4.0.1", "mkdirp": "^3.0.0", - "resolve-import": "^1.4.5", - "rimraf": "^5.0.5", - "sync-content": "^1.0.1", - "tap-parser": "16.0.1", - "tshy": "^1.14.0", - "typescript": "5.4", - "walk-up-path": "^3.0.1" + "package-json-from-dist": "^1.0.0", + "resolve-import": "^2.0.0", + "rimraf": "^6.0.0", + "sync-content": "^2.0.1", + "tap-parser": "18.0.1", + "tshy": "^3.0.3", + "typescript": "5.9", + "walk-up-path": "^4.0.0" }, "bin": { - "generate-tap-test-class": "scripts/build.mjs" + "generate-tap-test-class": "dist/esm/build.mjs" }, "engines": { - "node": "16 >=16.17.0 || 18 >= 18.6.0 || >=20" + "node": "20 || >=22" }, "peerDependencies": { - "@tapjs/core": "2.0.1" - } - }, - "node_modules/@tapjs/test/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" + "@tapjs/core": "4.0.2" } }, - "node_modules/@tapjs/test/node_modules/glob": { - "version": "10.4.1", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.1.tgz", - "integrity": "sha512-2jelhlq3E4ho74ZyVLN03oKdAZVUa6UDZzFLVH1H7dnoax+y9qyaq8zBkfDIggjniU19z0wU18y16jMB2eyVIw==", + "node_modules/@tapjs/test/node_modules/jackspeak": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.1.1.tgz", + "integrity": "sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==", "dev": true, + "license": "BlueOak-1.0.0", "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" + "@isaacs/cliui": "^8.0.2" }, "engines": { - "node": ">=16 || 14 >=14.18" + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@tapjs/test/node_modules/minimatch": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", - "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", + "node_modules/@tapjs/test/node_modules/rimraf": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-6.0.1.tgz", + "integrity": "sha512-9dkvaxAsk/xNXSJzMgFqqMCuFgt2+KsOFek3TMLfo8NCPfWpBmqwyNn5Y+NX56QUYfCtsyhF3ayiboEoUmJk/A==", "dev": true, + "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.1" + "glob": "^11.0.0", + "package-json-from-dist": "^1.0.0" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/@tapjs/typescript": { - "version": "1.4.6", - "resolved": "https://registry.npmjs.org/@tapjs/typescript/-/typescript-1.4.6.tgz", - "integrity": "sha512-6jxUQ7Mdb+Y2q8RJcwgZZ6dCR+X2u3hCL+xb1GDAtO7k1+B6z2b+z+I+FdhuO4YgrP0SLRjocL5rJM/xi9K7qw==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@tapjs/typescript/-/typescript-3.1.1.tgz", + "integrity": "sha512-2fxDTeL8X3sm9g0KnHZSD/p9o8tFWYhswRKUq//jv9FA/4XetsKs+ApddPUJEi3AX8+Ma1P1EzjSy/f0z5KIJA==", "dev": true, + "license": "BlueOak-1.0.0", "dependencies": { "@isaacs/ts-node-temp-fork-for-pr-2009": "^10.9.7" }, "engines": { - "node": "16 >=16.17.0 || 18 >= 18.6.0 || >=20" + "node": "20 || >=22" }, "peerDependencies": { - "@tapjs/core": "2.0.1" + "@tapjs/core": "4.0.2" } }, "node_modules/@tapjs/worker": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@tapjs/worker/-/worker-2.0.1.tgz", - "integrity": "sha512-wegz8IxNEPIIAA+R76/avZgNmZ4iC7QGFbtXKGBU962/1lXTITxshRV6e21r0IBa7YLkSVgDuVSVB3+Qzve0Yg==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@tapjs/worker/-/worker-4.0.2.tgz", + "integrity": "sha512-o2jzyjEnpRf3xHmduIfO/HXb5m9qRz+tCoiiFmPav+LC7eilSrDP3eGiQwMGbew64PK52KQwQW/LYNRrQsuMxA==", "dev": true, + "license": "BlueOak-1.0.0", "engines": { - "node": "16 >=16.17.0 || 18 >= 18.6.0 || >=20" + "node": "20 || >=22" }, "peerDependencies": { - "@tapjs/core": "2.0.1" + "@tapjs/core": "4.0.2" } }, "node_modules/@tsconfig/node14": { - "version": "14.1.2", - "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-14.1.2.tgz", - "integrity": "sha512-1vncsbfCZ3TBLPxesRYz02Rn7SNJfbLoDVkcZ7F/ixOV6nwxwgdhD1mdPcc5YQ413qBJ8CvMxXMFfJ7oawjo7Q==", - "dev": true + "version": "14.1.5", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-14.1.5.tgz", + "integrity": "sha512-hsldDMdbjF18BgvqFX6rHwqb0wlDh4lxyXmo3VATa7LwL4AFrHijv8Or9ySXBSg9TyysRkldJyAC/kplyF/Mmg==", + "dev": true, + "license": "MIT" }, "node_modules/@tsconfig/node16": { - "version": "16.1.3", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-16.1.3.tgz", - "integrity": "sha512-9nTOUBn+EMKO6rtSZJk+DcqsfgtlERGT9XPJ5PRj/HNENPCBY1yu/JEj5wT6GLtbCLBO2k46SeXDaY0pjMqypw==", - "dev": true + "version": "16.1.5", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-16.1.5.tgz", + "integrity": "sha512-P4GBZdpi6i5NGB12pwLH4oAIPrxl1ZvD8gxA4E7VdUUCik34J7FerrxzoDXtT4ugbrRjLbK5WuHPOUNZJOxuaQ==", + "dev": true, + "license": "MIT" }, "node_modules/@tsconfig/node18": { "version": "18.2.4", "resolved": "https://registry.npmjs.org/@tsconfig/node18/-/node18-18.2.4.tgz", "integrity": "sha512-5xxU8vVs9/FNcvm3gE07fPbn9tl6tqGGWA9tSlwsUEkBxtRnTsNmwrV8gasZ9F/EobaSv9+nu8AxUKccw77JpQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@tsconfig/node20": { - "version": "20.1.4", - "resolved": "https://registry.npmjs.org/@tsconfig/node20/-/node20-20.1.4.tgz", - "integrity": "sha512-sqgsT69YFeLWf5NtJ4Xq/xAF8p4ZQHlmGW74Nu2tD4+g5fAsposc4ZfaaPixVu4y01BEiDCWLRDCvDM5JOsRxg==", - "dev": true + "version": "20.1.6", + "resolved": "https://registry.npmjs.org/@tsconfig/node20/-/node20-20.1.6.tgz", + "integrity": "sha512-sz+Hqx9zwZDpZIV871WSbUzSqNIsXzghZydypnfgzPKLltVJfkINfUeTct31n/tTSa9ZE1ZOfKdRre1uHHquYQ==", + "dev": true, + "license": "MIT" }, "node_modules/@tufjs/canonical-json": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@tufjs/canonical-json/-/canonical-json-2.0.0.tgz", "integrity": "sha512-yVtV8zsdo8qFHe+/3kw81dSLyF7D576A5cCFCi4X7B39tWT7SekaEFUnvnWJHz+9qO7qJTah1JbrDjWKqFtdWA==", "dev": true, + "license": "MIT", "engines": { "node": "^16.14.0 || >=18.0.0" } }, "node_modules/@tufjs/models": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@tufjs/models/-/models-2.0.1.tgz", - "integrity": "sha512-92F7/SFyufn4DXsha9+QfKnN03JGqtMFMXgSHbZOo8JG59WkTni7UzAouNQDf7AuP9OAMxVOPQcqG3sB7w+kkg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@tufjs/models/-/models-4.0.0.tgz", + "integrity": "sha512-h5x5ga/hh82COe+GoD4+gKUeV4T3iaYOxqLt41GRKApinPI7DMidhCmNVTjKfhCWFJIGXaFJee07XczdT4jdZQ==", "dev": true, + "license": "MIT", "dependencies": { "@tufjs/canonical-json": "2.0.0", - "minimatch": "^9.0.4" + "minimatch": "^9.0.5" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/@tufjs/models/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==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" } }, "node_modules/@tufjs/models/node_modules/minimatch": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", - "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "dev": true, + "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" }, @@ -1480,35 +1804,51 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/@types/estree": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", + "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", + "license": "MIT" + }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", - "dev": true + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "license": "MIT" }, "node_modules/@types/node": { - "version": "20.13.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.13.0.tgz", - "integrity": "sha512-FM6AOb3khNkNIXPnHFDYaHerSv8uN22C91z098AnGccVu+Pcdhi+pNUFDi0iLmPIsVE0JBD0KVS7mzUYt4nRzQ==", + "version": "24.7.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.7.2.tgz", + "integrity": "sha512-/NbVmcGTP+lj5oa4yiYxxeBjRivKQ5Ns1eSZeB99ExsEQ6rX5XYU1Zy/gGxY/ilqtD4Etx9mKyrPxZRetiahhA==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { - "undici-types": "~5.26.4" + "undici-types": "~7.14.0" } }, "node_modules/abbrev": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz", - "integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-3.0.1.tgz", + "integrity": "sha512-AO2ac6pjRB3SJmGJo+v5/aK6Omggp6fsLrs6wN9bd35ulu4cCwaAU9+7ZhXjeqHVkaHThLuzH0nZr0YpCDhygg==", "dev": true, + "license": "ISC", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/acorn": { - "version": "8.8.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", - "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "license": "MIT", "bin": { "acorn": "bin/acorn" }, @@ -1520,57 +1860,39 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "license": "MIT", "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "node_modules/acorn-walk": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", - "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, "engines": { "node": ">=0.4.0" } }, "node_modules/agent-base": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", - "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", "dev": true, - "dependencies": { - "debug": "^4.3.4" - }, + "license": "MIT", "engines": { "node": ">= 14" } }, - "node_modules/aggregate-error": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", - "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", - "dev": true, - "dependencies": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/aggregate-error/node_modules/indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -1583,12 +1905,16 @@ } }, "node_modules/ansi-escapes": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-6.2.1.tgz", - "integrity": "sha512-4nJ3yixlEthEJ9Rk4vPcdBRkZvQZlYyu8j4/Mqz5sgIkddmEnH2Yj2ZrnP9S3tQOvSNRUIgVNF/1yPpRAGNRig==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.1.1.tgz", + "integrity": "sha512-Zhl0ErHcSRUaVfGUeUdDuLgpkEo8KIFjB4Y9uAc46ScOpdDiU1Dbyplh7qWJeJ/ZHpbyMSM26+X3BySgnIz40Q==", "dev": true, + "license": "MIT", + "dependencies": { + "environment": "^1.0.0" + }, "engines": { - "node": ">=14.16" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -1633,7 +1959,8 @@ "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/argparse": { "version": "2.0.1", @@ -1645,6 +1972,7 @@ "resolved": "https://registry.npmjs.org/async-hook-domain/-/async-hook-domain-4.0.1.tgz", "integrity": "sha512-bSktexGodAjfHWIrSrrqxqWzf1hWBZBpmPNZv+TYUMyWa2eoefFc6q6H1+KtdHYSz35lrhWdmXt/XK9wNEZvww==", "dev": true, + "license": "ISC", "engines": { "node": ">=16" } @@ -1654,6 +1982,7 @@ "resolved": "https://registry.npmjs.org/auto-bind/-/auto-bind-5.0.1.tgz", "integrity": "sha512-ooviqdwwgfIfNmDwo94wlshcdzfO64XV0Cg6oDsDYBJfITDz1EngD2z7DkbvCWn+XIMsIqW27sEVF6qcpJrRcg==", "dev": true, + "license": "MIT", "engines": { "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, @@ -1676,9 +2005,10 @@ } }, "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -1698,19 +2028,20 @@ } }, "node_modules/c8": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/c8/-/c8-9.1.0.tgz", - "integrity": "sha512-mBWcT5iqNir1zIkzSPyI3NCR9EZCVI3WUD+AVO17MVWTSFNyUueXE82qTeampNtTr+ilN/5Ua3j24LgbCKjDVg==", + "version": "10.1.3", + "resolved": "https://registry.npmjs.org/c8/-/c8-10.1.3.tgz", + "integrity": "sha512-LvcyrOAaOnrrlMpW22n690PUvxiq4Uf9WMhQwNJ9vgagkL/ph1+D4uvjvDA5XCbykrc0sx+ay6pVi9YZ1GnhyA==", "dev": true, + "license": "ISC", "dependencies": { - "@bcoe/v8-coverage": "^0.2.3", + "@bcoe/v8-coverage": "^1.0.1", "@istanbuljs/schema": "^0.1.3", "find-up": "^5.0.0", "foreground-child": "^3.1.1", "istanbul-lib-coverage": "^3.2.0", "istanbul-lib-report": "^3.0.1", "istanbul-reports": "^3.1.6", - "test-exclude": "^6.0.0", + "test-exclude": "^7.0.1", "v8-to-istanbul": "^9.0.0", "yargs": "^17.7.2", "yargs-parser": "^21.1.1" @@ -1719,82 +2050,55 @@ "c8": "bin/c8.js" }, "engines": { - "node": ">=14.14.0" + "node": ">=18" + }, + "peerDependencies": { + "monocart-coverage-reports": "^2" + }, + "peerDependenciesMeta": { + "monocart-coverage-reports": { + "optional": true + } } }, "node_modules/cacache": { - "version": "18.0.3", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-18.0.3.tgz", - "integrity": "sha512-qXCd4rh6I07cnDqh8V48/94Tc/WSfj+o3Gn6NZ0aZovS255bUx8O13uKxRFd2eWG0xgsco7+YItQNPaa5E85hg==", + "version": "20.0.1", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-20.0.1.tgz", + "integrity": "sha512-+7LYcYGBYoNqTp1Rv7Ny1YjUo5E0/ftkQtraH3vkfAGgVHc+ouWdC8okAwQgQR7EVIdW6JTzTmhKFwzb+4okAQ==", "dev": true, + "license": "ISC", "dependencies": { - "@npmcli/fs": "^3.1.0", + "@npmcli/fs": "^4.0.0", "fs-minipass": "^3.0.0", - "glob": "^10.2.2", - "lru-cache": "^10.0.1", + "glob": "^11.0.3", + "lru-cache": "^11.1.0", "minipass": "^7.0.3", "minipass-collect": "^2.0.1", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", - "p-map": "^4.0.0", - "ssri": "^10.0.0", - "tar": "^6.1.11", - "unique-filename": "^3.0.0" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "node_modules/cacache/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/cacache/node_modules/glob": { - "version": "10.4.1", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.1.tgz", - "integrity": "sha512-2jelhlq3E4ho74ZyVLN03oKdAZVUa6UDZzFLVH1H7dnoax+y9qyaq8zBkfDIggjniU19z0wU18y16jMB2eyVIw==", - "dev": true, - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" + "p-map": "^7.0.2", + "ssri": "^12.0.0", + "unique-filename": "^4.0.0" }, "engines": { - "node": ">=16 || 14 >=14.18" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/cacache/node_modules/minimatch": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", - "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", + "node_modules/cacache/node_modules/lru-cache": { + "version": "11.2.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.2.tgz", + "integrity": "sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==", "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, + "license": "ISC", "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": "20 || >=22" } }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "license": "MIT", "engines": { "node": ">=6" } @@ -1851,12 +2155,13 @@ } }, "node_modules/chownr": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", - "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", "dev": true, + "license": "BlueOak-1.0.0", "engines": { - "node": ">=10" + "node": ">=18" } }, "node_modules/chroma-js": { @@ -1864,35 +2169,12 @@ "resolved": "https://registry.npmjs.org/chroma-js/-/chroma-js-2.4.2.tgz", "integrity": "sha512-U9eDw6+wt7V8z5NncY2jJfZa+hUH8XEj8FQHgFJTrUFnJfXYf4Ml4adI2vXZOjqRDpFWtYVWypDfZwnJ+HIR4A==" }, - "node_modules/ci-info": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", - "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], - "engines": { - "node": ">=8" - } - }, - "node_modules/clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/cli-boxes": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz", "integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==", "dev": true, + "license": "MIT", "engines": { "node": ">=10" }, @@ -1905,6 +2187,7 @@ "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz", "integrity": "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==", "dev": true, + "license": "MIT", "dependencies": { "restore-cursor": "^4.0.0" }, @@ -1916,26 +2199,41 @@ } }, "node_modules/cli-truncate": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-3.1.0.tgz", - "integrity": "sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-4.0.0.tgz", + "integrity": "sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==", "dev": true, + "license": "MIT", "dependencies": { "slice-ansi": "^5.0.0", - "string-width": "^5.0.0" + "string-width": "^7.0.0" }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/cli-truncate/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, "node_modules/cli-truncate/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==", + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", "dev": true, + "license": "MIT", "engines": { "node": ">=12" }, @@ -1943,11 +2241,19 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/cli-truncate/node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "dev": true, + "license": "MIT" + }, "node_modules/cli-truncate/node_modules/slice-ansi": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^6.0.0", "is-fullwidth-code-point": "^4.0.0" @@ -1959,11 +2265,46 @@ "url": "https://github.com/chalk/slice-ansi?sponsor=1" } }, + "node_modules/cli-truncate/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate/node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", "dev": true, + "license": "ISC", "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", @@ -1977,13 +2318,15 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/cliui/node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -1993,6 +2336,7 @@ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, + "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -2007,6 +2351,7 @@ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -2024,6 +2369,7 @@ "resolved": "https://registry.npmjs.org/code-excerpt/-/code-excerpt-4.0.0.tgz", "integrity": "sha512-xxodCmBen3iy2i0WtAK8FlFNrRzjUqjRsMfho58xT/wvZU1YTM3fCnRjcy1gJPMepaRlgm/0e6w8SpWHpn3/cA==", "dev": true, + "license": "MIT", "dependencies": { "convert-to-spaces": "^2.0.1" }, @@ -2053,9 +2399,10 @@ "integrity": "sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w==" }, "node_modules/compress-json": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/compress-json/-/compress-json-3.0.5.tgz", - "integrity": "sha512-HYiJvE0cTIygI9zXqY5fkRr7H3NV3UAME0enzwN5M0JkzMOtUcjSyaH7HxVRzXsn7IIXD0STA9M5jyWkxERSLg==" + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/compress-json/-/compress-json-3.4.0.tgz", + "integrity": "sha512-SxT8uFOacRbP3gfi4aVAulr4KzE933eZWBguVBwuVWv3GTGQAToqpq8rFtdbIVgeCOH7RpPuXKQrPPd087b8uA==", + "license": "BSD-2-Clause" }, "node_modules/concat-map": { "version": "0.0.1", @@ -2066,21 +2413,24 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/convert-to-spaces": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/convert-to-spaces/-/convert-to-spaces-2.0.1.tgz", "integrity": "sha512-rcQ1bsQO9799wq24uE5AM2tAILy4gXGIK/njFWcVQkGNZ96edlpY+A7bjwvzjYvLDyzmG1MmMLZhpcsb+klNMQ==", "dev": true, + "license": "MIT", "engines": { "node": "^12.20.0 || ^14.13.1 || >=16.0.0" } }, "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -2091,11 +2441,12 @@ } }, "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", "dependencies": { - "ms": "2.1.2" + "ms": "^2.1.3" }, "engines": { "node": ">=6.0" @@ -2109,7 +2460,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==", + "license": "MIT" }, "node_modules/diff": { "version": "5.2.0", @@ -2120,17 +2472,6 @@ "node": ">=0.3.1" } }, - "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -2146,6 +2487,7 @@ "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", "dev": true, + "license": "MIT", "optional": true, "dependencies": { "iconv-lite": "^0.6.2" @@ -2156,21 +2498,48 @@ "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } }, + "node_modules/environment": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", + "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/err-code": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", - "dev": true + "dev": true, + "license": "MIT" + }, + "node_modules/es-toolkit": { + "version": "1.40.0", + "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.40.0.tgz", + "integrity": "sha512-8o6w0KFmU0CiIl0/Q/BCEOabF2IJaELM1T2PWj6e8KqzHv1gdx+7JtFnDwOx1kJH/isJ5NwlDG1nCr1HrRF94Q==", + "dev": true, + "license": "MIT", + "workspaces": [ + "docs", + "benchmarks" + ] }, "node_modules/escalade": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", - "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -2187,95 +2556,105 @@ } }, "node_modules/eslint": { - "version": "8.37.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.37.0.tgz", - "integrity": "sha512-NU3Ps9nI05GUoVMxcZx1J8CNR6xOvUT4jAUMH5+z8lpp3aEdPVCImKw6PWG4PY+Vfkpr+jvMpxs/qoE7wq0sPw==", - "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.4.0", - "@eslint/eslintrc": "^2.0.2", - "@eslint/js": "8.37.0", - "@humanwhocodes/config-array": "^0.11.8", + "version": "9.37.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.37.0.tgz", + "integrity": "sha512-XyLmROnACWqSxiGYArdef1fItQd47weqB7iwtfr9JHwRrqIXZdcFMvvEcL9xHCmL0SNsOvF0c42lWyM1U5dgig==", + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.0", + "@eslint/config-helpers": "^0.4.0", + "@eslint/core": "^0.16.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.37.0", + "@eslint/plugin-kit": "^0.4.0", + "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", - "ajv": "^6.10.0", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", + "ajv": "^6.12.4", "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", + "cross-spawn": "^7.0.6", "debug": "^4.3.2", - "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.1.1", - "eslint-visitor-keys": "^3.4.0", - "espree": "^9.5.1", - "esquery": "^1.4.2", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", + "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "grapheme-splitter": "^1.0.4", "ignore": "^5.2.0", - "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", - "js-sdsl": "^4.1.4", - "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", - "optionator": "^0.9.1", - "strip-ansi": "^6.0.1", - "strip-json-comments": "^3.1.0", - "text-table": "^0.2.0" + "optionator": "^0.9.3" }, "bin": { "eslint": "bin/eslint.js" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } } }, "node_modules/eslint-scope": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", - "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "license": "BSD-2-Clause", "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, "node_modules/eslint-visitor-keys": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.0.tgz", - "integrity": "sha512-HPpKPUBQcAsZOsHAFwTtIKcYlCje62XB7SEAcxjtmW6TD1WVpkS6i6/hOVtTZIl4zGj/mBqpFVGvaDneik+VoQ==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "license": "Apache-2.0", "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://opencollective.com/eslint" } }, "node_modules/espree": { - "version": "9.5.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.1.tgz", - "integrity": "sha512-5yxtHSZXRSW5pvv3hAlXM5+/Oswi1AUFqBmbibKb5s6bp3rGIDkyXU6xCoyuuLhijr4SFwPrXRoZjz0AZDN9tg==", + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "license": "BSD-2-Clause", "dependencies": { - "acorn": "^8.8.0", + "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.0" + "eslint-visitor-keys": "^4.2.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://opencollective.com/eslint" @@ -2296,6 +2675,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "license": "BSD-2-Clause", "dependencies": { "estraverse": "^5.2.0" }, @@ -2315,6 +2695,7 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "license": "BSD-2-Clause", "engines": { "node": ">=0.10.0" } @@ -2324,48 +2705,46 @@ "resolved": "https://registry.npmjs.org/events-to-array/-/events-to-array-2.0.3.tgz", "integrity": "sha512-f/qE2gImHRa4Cp2y1stEOSgw8wTFyUdVJX7G//bMwbaV9JqISFxg99NbmVQeP7YLnDUZ2un851jlaDrlpmGehQ==", "dev": true, + "license": "ISC", "engines": { "node": ">=12" } }, "node_modules/exponential-backoff": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.1.tgz", - "integrity": "sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw==", - "dev": true + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.3.tgz", + "integrity": "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==", + "dev": true, + "license": "Apache-2.0" }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "license": "MIT" }, "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==" - }, - "node_modules/fastq": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", - "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", - "dependencies": { - "reusify": "^1.0.4" - } + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "license": "MIT" }, "node_modules/file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "license": "MIT", "dependencies": { - "flat-cache": "^3.0.4" + "flat-cache": "^4.0.0" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": ">=16.0.0" } }, "node_modules/fill-range": { @@ -2397,37 +2776,23 @@ } }, "node_modules/flat-cache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "license": "MIT", "dependencies": { - "flatted": "^3.1.0", - "rimraf": "^3.0.2" + "flatted": "^3.2.9", + "keyv": "^4.5.4" }, "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/flat-cache/node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">=16" } }, "node_modules/flatted": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.5.tgz", - "integrity": "sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg==" + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "license": "ISC" }, "node_modules/flexsearch": { "version": "0.7.43", @@ -2435,11 +2800,12 @@ "integrity": "sha512-c5o/+Um8aqCSOXGcZoqZOm+NqtVwNsvVpWv6lfmSclU954O3wvQKxxK8zj74fPaSJbXpSLTs4PRhh+wnoCXnKg==" }, "node_modules/foreground-child": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", - "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "license": "ISC", "dependencies": { - "cross-spawn": "^7.0.0", + "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" }, "engines": { @@ -2467,13 +2833,15 @@ "type": "consulting", "url": "https://feross.org/support" } - ] + ], + "license": "MIT" }, "node_modules/fs-minipass": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.3.tgz", "integrity": "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==", "dev": true, + "license": "ISC", "dependencies": { "minipass": "^7.0.3" }, @@ -2481,11 +2849,6 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" - }, "node_modules/fsevents": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", @@ -2500,44 +2863,55 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/function-loop": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/function-loop/-/function-loop-4.0.0.tgz", "integrity": "sha512-f34iQBedYF3XcI93uewZZOnyscDragxgTK/eTvVB74k3fCD0ZorOi5BV9GS4M8rz/JoNi0Kl3qX5Y9MH3S/CLQ==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true, + "license": "ISC", "engines": { "node": "6.* || 8.* || >= 10.*" } }, + "node_modules/get-east-asian-width": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.4.0.tgz", + "integrity": "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.3.tgz", + "integrity": "sha512-2Nim7dha1KVkaiF4q6Dj+ngPPMdfvLJEOpZk/jKiUAkqKebpGAWQXAq9z1xu9HKu5lWfqw/FASuccEjyznjPaA==", + "dev": true, + "license": "ISC", "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "foreground-child": "^3.3.1", + "jackspeak": "^4.1.1", + "minimatch": "^10.0.3", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^2.0.0" + }, + "bin": { + "glob": "dist/esm/bin.mjs" }, "engines": { - "node": "*" + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -2554,15 +2928,72 @@ "node": ">=10.13.0" } }, - "node_modules/globals": { - "version": "13.20.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", - "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", + "node_modules/glob/node_modules/jackspeak": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.1.1.tgz", + "integrity": "sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==", + "dev": true, + "license": "BlueOak-1.0.0", "dependencies": { - "type-fest": "^0.20.2" + "@isaacs/cliui": "^8.0.2" }, "engines": { - "node": ">=8" + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob/node_modules/lru-cache": { + "version": "11.2.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.2.tgz", + "integrity": "sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.3.tgz", + "integrity": "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==", + "dev": true, + "license": "ISC", + "dependencies": { + "@isaacs/brace-expansion": "^5.0.0" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob/node_modules/path-scurry": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", + "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "license": "MIT", + "engines": { + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -2572,12 +3003,8 @@ "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true - }, - "node_modules/grapheme-splitter": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", - "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==" + "dev": true, + "license": "ISC" }, "node_modules/has-flag": { "version": "4.0.0", @@ -2587,55 +3014,49 @@ "node": ">=8" } }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "node_modules/hosted-git-info": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-9.0.2.tgz", + "integrity": "sha512-M422h7o/BR3rmCQ8UHi7cyyMqKltdP9Uo+J2fXK+RSAY+wTcKOIRyhTuKv4qn+DJf3g+PL890AzId5KZpX+CBg==", "dev": true, + "license": "ISC", "dependencies": { - "function-bind": "^1.1.2" + "lru-cache": "^11.1.0" }, "engines": { - "node": ">= 0.4" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "bin": { - "he": "bin/he" - } - }, - "node_modules/hosted-git-info": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz", - "integrity": "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==", + "node_modules/hosted-git-info/node_modules/lru-cache": { + "version": "11.2.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.2.tgz", + "integrity": "sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==", "dev": true, - "dependencies": { - "lru-cache": "^10.0.1" - }, + "license": "ISC", "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "20 || >=22" } }, "node_modules/html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/http-cache-semantics": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", - "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", - "dev": true + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", + "dev": true, + "license": "BSD-2-Clause" }, "node_modules/http-proxy-agent": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", "dev": true, + "license": "MIT", "dependencies": { "agent-base": "^7.1.0", "debug": "^4.3.4" @@ -2645,12 +3066,13 @@ } }, "node_modules/https-proxy-agent": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz", - "integrity": "sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", "dev": true, + "license": "MIT", "dependencies": { - "agent-base": "^7.0.2", + "agent-base": "^7.1.2", "debug": "4" }, "engines": { @@ -2662,6 +3084,7 @@ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", "dev": true, + "license": "MIT", "optional": true, "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" @@ -2671,67 +3094,60 @@ } }, "node_modules/ignore": { - "version": "5.2.4", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", - "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "license": "MIT", "engines": { "node": ">= 4" } }, "node_modules/ignore-walk": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-6.0.5.tgz", - "integrity": "sha512-VuuG0wCnjhnylG1ABXT3dAuIpTNDs/G8jlpmwXY03fXoXy/8ZK8/T+hMzt8L4WnrLCJgdybqgPagnF/f97cg3A==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-8.0.0.tgz", + "integrity": "sha512-FCeMZT4NiRQGh+YkeKMtWrOmBgWjHjMJ26WQWrRQyoyzqevdaGSakUaJW5xQYmjLlUVk2qUnCjYVBax9EKKg8A==", "dev": true, + "license": "ISC", "dependencies": { - "minimatch": "^9.0.0" + "minimatch": "^10.0.3" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/ignore-walk/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/ignore-walk/node_modules/minimatch": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", - "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.3.tgz", + "integrity": "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==", "dev": true, + "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.1" + "@isaacs/brace-expansion": "^5.0.0" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/image-size": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/image-size/-/image-size-1.0.2.tgz", - "integrity": "sha512-xfOoWjceHntRb3qFCrh5ZFORYH8XCdYpASltMhZ/Q0KZiOwjdE/Yl2QCiWdwD+lygV5bMCvauzgu5PxBX/Yerg==", - "dependencies": { - "queue": "6.0.2" - }, + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/image-size/-/image-size-2.0.2.tgz", + "integrity": "sha512-IRqXKlaXwgSMAMtpNzZa1ZAe8m+Sa1770Dhk8VkSsP9LS+iHD62Zd8FQKs8fbPiagBE7BzoFX23cxFnwshpV6w==", + "license": "MIT", "bin": { "image-size": "bin/image-size.js" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.x" } }, "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "license": "MIT", "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" @@ -2756,6 +3172,7 @@ "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz", "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==", "dev": true, + "license": "MIT", "engines": { "node": ">=12" }, @@ -2763,54 +3180,50 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" + "node_modules/ini": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-5.0.0.tgz", + "integrity": "sha512-+N0ngpO3e7cRUWOJAS7qw0IZIVc6XPrW4MlFBdD066F2L4k1L6ker3hLqSq7iXxU5tgS4WGkIUElWn5vogAEnw==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, "node_modules/ink": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/ink/-/ink-4.4.1.tgz", - "integrity": "sha512-rXckvqPBB0Krifk5rn/5LvQGmyXwCUpBfmTwbkQNBY9JY8RSl3b8OftBNEYxg4+SWUhEKcPifgope28uL9inlA==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/ink/-/ink-5.2.1.tgz", + "integrity": "sha512-BqcUyWrG9zq5HIwW6JcfFHsIYebJkWWb4fczNah1goUO0vv5vneIlfwuS85twyJ5hYR/y18FlAYUxrO9ChIWVg==", "dev": true, + "license": "MIT", "dependencies": { "@alcalzone/ansi-tokenize": "^0.1.3", - "ansi-escapes": "^6.0.0", + "ansi-escapes": "^7.0.0", + "ansi-styles": "^6.2.1", "auto-bind": "^5.0.1", - "chalk": "^5.2.0", + "chalk": "^5.3.0", "cli-boxes": "^3.0.0", "cli-cursor": "^4.0.0", - "cli-truncate": "^3.1.0", + "cli-truncate": "^4.0.0", "code-excerpt": "^4.0.0", + "es-toolkit": "^1.22.0", "indent-string": "^5.0.0", - "is-ci": "^3.0.1", - "is-lower-case": "^2.0.2", - "is-upper-case": "^2.0.2", - "lodash": "^4.17.21", + "is-in-ci": "^1.0.0", "patch-console": "^2.0.0", "react-reconciler": "^0.29.0", "scheduler": "^0.23.0", "signal-exit": "^3.0.7", - "slice-ansi": "^6.0.0", + "slice-ansi": "^7.1.0", "stack-utils": "^2.0.6", - "string-width": "^5.1.2", - "type-fest": "^0.12.0", - "widest-line": "^4.0.1", - "wrap-ansi": "^8.1.0", - "ws": "^8.12.0", - "yoga-wasm-web": "~0.3.3" + "string-width": "^7.2.0", + "type-fest": "^4.27.0", + "widest-line": "^5.0.0", + "wrap-ansi": "^9.0.0", + "ws": "^8.18.0", + "yoga-layout": "~3.2.1" }, "engines": { - "node": ">=14.16" + "node": ">=18" }, "peerDependencies": { "@types/react": ">=18.0.0", @@ -2826,11 +3239,38 @@ } } }, + "node_modules/ink/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ink/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/ink/node_modules/chalk": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", - "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", "dev": true, + "license": "MIT", "engines": { "node": "^12.17.0 || ^14.13 || >=16.0.0" }, @@ -2838,33 +3278,78 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/ink/node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "dev": true, + "license": "MIT" + }, "node_modules/ink/node_modules/signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true + "dev": true, + "license": "ISC" }, - "node_modules/ink/node_modules/type-fest": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.12.0.tgz", - "integrity": "sha512-53RyidyjvkGpnWPMF9bQgFtWp+Sl8O2Rp13VavmJgfAP9WWG6q6TkrKU8iyJdnwnfgHI6k2hTlgqH4aSdjoTbg==", + "node_modules/ink/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, "engines": { - "node": ">=10" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/ip-address": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", - "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", + "node_modules/ink/node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/ink/node_modules/wrap-ansi": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", "dev": true, + "license": "MIT", "dependencies": { - "jsbn": "1.1.0", - "sprintf-js": "^1.1.3" + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/ip-address": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz", + "integrity": "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==", + "dev": true, + "license": "MIT", "engines": { "node": ">= 12" } @@ -2873,7 +3358,8 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-actual-promise/-/is-actual-promise-1.0.2.tgz", "integrity": "sha512-xsFiO1of0CLsQnPZ1iXHNTyR9YszOeWKYv+q6n8oSFW3ipooFJ1j1lbRMgiMCr+pp2gLruESI4zb5Ak6eK5OnQ==", - "dev": true + "dev": true, + "license": "BlueOak-1.0.0" }, "node_modules/is-binary-path": { "version": "2.1.0", @@ -2887,30 +3373,6 @@ "node": ">=8" } }, - "node_modules/is-ci": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.1.tgz", - "integrity": "sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==", - "dev": true, - "dependencies": { - "ci-info": "^3.2.0" - }, - "bin": { - "is-ci": "bin.js" - } - }, - "node_modules/is-core-module": { - "version": "2.13.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", - "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", - "dev": true, - "dependencies": { - "hasown": "^2.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -2924,6 +3386,7 @@ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=12" }, @@ -2942,19 +3405,20 @@ "node": ">=0.10.0" } }, - "node_modules/is-lambda": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", - "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==", - "dev": true - }, - "node_modules/is-lower-case": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-lower-case/-/is-lower-case-2.0.2.tgz", - "integrity": "sha512-bVcMJy4X5Og6VZfdOZstSexlEy20Sr0k/p/b2IlQJlfdKAQuMpiv5w2Ccxb8sKdRUNAG1PnHVHjFSdRDVS6NlQ==", + "node_modules/is-in-ci": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-in-ci/-/is-in-ci-1.0.0.tgz", + "integrity": "sha512-eUuAjybVTHMYWm/U+vBO1sY/JOCgoPCXRxzdju0K+K0BiGW0SChEL1MLC0PoCIR1OlPo5YAp8HuQoUlsWEICwg==", "dev": true, - "dependencies": { - "tslib": "^2.0.3" + "license": "MIT", + "bin": { + "is-in-ci": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/is-number": { @@ -2967,14 +3431,6 @@ "node": ">=0.12.0" } }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "engines": { - "node": ">=8" - } - }, "node_modules/is-plain-object": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", @@ -2984,15 +3440,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-upper-case": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-upper-case/-/is-upper-case-2.0.2.tgz", - "integrity": "sha512-44pxmxAvnnAOwBg4tHPnkfvgjPwbc5QIsSstNU+YcJ1ovxVzCWpSGosPJOZh/a1tdl81fbgnLc9LLv+x2ywbPQ==", - "dev": true, - "dependencies": { - "tslib": "^2.0.3" - } - }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -3003,6 +3450,7 @@ "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", "dev": true, + "license": "BSD-3-Clause", "engines": { "node": ">=8" } @@ -3012,6 +3460,7 @@ "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "istanbul-lib-coverage": "^3.0.0", "make-dir": "^4.0.0", @@ -3022,10 +3471,11 @@ } }, "node_modules/istanbul-reports": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", - "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "html-escaper": "^2.0.0", "istanbul-lib-report": "^3.0.0" @@ -3051,15 +3501,6 @@ "@pkgjs/parseargs": "^0.11.0" } }, - "node_modules/js-sdsl": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.4.0.tgz", - "integrity": "sha512-FfVSdx6pJ41Oa+CF7RDaFmTnCaFhua+SNYQX74riGOpl96x+2jQCqEfQ2bnXu/5DPCqlRuiqyvTJM0Qjz26IVg==", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/js-sdsl" - } - }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -3082,25 +3523,27 @@ "resolved": "https://registry.npmjs.org/jsbi/-/jsbi-4.3.0.tgz", "integrity": "sha512-SnZNcinB4RIcnEyZqFPdGPVgrg2AcnykiBy0sHVJQKHYeaLUvi3Exj+iaPpLnFVkDPZIV4U0yvgC9/R4uEAZ9g==" }, - "node_modules/jsbn": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", - "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==", - "dev": true + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "license": "MIT" }, "node_modules/json-parse-even-better-errors": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.2.tgz", - "integrity": "sha512-fi0NG4bPjCHunUJffmLd0gxssIgkNmArMvis4iNah6Owg1MCJjWhEcDLmsK6iGkJq3tHwbDkTlce70/tmXN4cQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-4.0.0.tgz", + "integrity": "sha512-lR4MXjGNgkJc7tkQ97kb2nuEMnNCyU//XYVH0MKTGcXEiSudQ5MKGKen3C5QubYy0vmq+JGitUg92uuywGEwIA==", "dev": true, + "license": "MIT", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "license": "MIT" }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", @@ -3114,12 +3557,23 @@ "dev": true, "engines": [ "node >= 0.2.0" - ] + ], + "license": "MIT" + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "license": "MIT", "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" @@ -3142,12 +3596,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true - }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -3178,6 +3626,7 @@ "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", "dev": true, + "license": "MIT", "dependencies": { "semver": "^7.5.3" }, @@ -3192,40 +3641,113 @@ "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/make-fetch-happen": { - "version": "13.0.1", - "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-13.0.1.tgz", - "integrity": "sha512-cKTUFc/rbKUd/9meOvgrpJ2WrNzymt6jfRDdwg5UCnVzv9dTpEj9JS5m3wtziXVCjluIXyL8pcaukYqezIzZQA==", + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-14.0.3.tgz", + "integrity": "sha512-QMjGbFTP0blj97EeidG5hk/QhKQ3T4ICckQGLgz38QF7Vgbk6e6FTARN8KhKxyBbWn8R0HU+bnw8aSoFPD4qtQ==", "dev": true, + "license": "ISC", "dependencies": { - "@npmcli/agent": "^2.0.0", - "cacache": "^18.0.0", + "@npmcli/agent": "^3.0.0", + "cacache": "^19.0.1", "http-cache-semantics": "^4.1.1", - "is-lambda": "^1.0.1", "minipass": "^7.0.2", - "minipass-fetch": "^3.0.0", + "minipass-fetch": "^4.0.0", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", - "negotiator": "^0.6.3", - "proc-log": "^4.2.0", + "negotiator": "^1.0.0", + "proc-log": "^5.0.0", "promise-retry": "^2.0.1", - "ssri": "^10.0.0" + "ssri": "^12.0.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/make-fetch-happen/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/make-fetch-happen/node_modules/cacache": { + "version": "19.0.1", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-19.0.1.tgz", + "integrity": "sha512-hdsUxulXCi5STId78vRVYEtDAjq99ICAUktLTeTYsLoTE6Z8dS0c8pWNCxwdrk9YfJeobDZc2Y186hD/5ZQgFQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/fs": "^4.0.0", + "fs-minipass": "^3.0.0", + "glob": "^10.2.2", + "lru-cache": "^10.0.1", + "minipass": "^7.0.3", + "minipass-collect": "^2.0.1", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "p-map": "^7.0.2", + "ssri": "^12.0.0", + "tar": "^7.4.3", + "unique-filename": "^4.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/make-fetch-happen/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/make-fetch-happen/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/marked": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/marked/-/marked-10.0.0.tgz", - "integrity": "sha512-YiGcYcWj50YrwBgNzFoYhQ1hT6GmQbFG8SksnYJX1z4BXTHSOrz1GB5/Jm2yQvMg4nN1FHP4M6r03R10KrVUiA==", + "version": "16.4.1", + "resolved": "https://registry.npmjs.org/marked/-/marked-16.4.1.tgz", + "integrity": "sha512-ntROs7RaN3EvWfy3EZi14H4YxmT6A5YvywfhO+0pm+cH/dnSQRmdAmoFIc3B9aiwTehyk7pESH4ofyBY+V5hZg==", + "license": "MIT", "bin": { "marked": "bin/marked.js" }, "engines": { - "node": ">= 18" + "node": ">= 20" } }, "node_modules/mimic-fn": { @@ -3233,6 +3755,7 @@ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -3261,6 +3784,7 @@ "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-2.0.1.tgz", "integrity": "sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==", "dev": true, + "license": "ISC", "dependencies": { "minipass": "^7.0.3" }, @@ -3269,17 +3793,18 @@ } }, "node_modules/minipass-fetch": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-3.0.5.tgz", - "integrity": "sha512-2N8elDQAtSnFV0Dk7gt15KHsS0Fyz6CbYZ360h0WTYV1Ty46li3rAXVOQj1THMNLdmrD9Vt5pBPtWtVkpwGBqg==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-4.0.1.tgz", + "integrity": "sha512-j7U11C5HXigVuutxebFadoYBbd7VSdZWggSe64NVdvWNBqGAiXPL2QVCehjmw7lY1oF9gOllYbORh+hiNgfPgQ==", "dev": true, + "license": "MIT", "dependencies": { "minipass": "^7.0.3", "minipass-sized": "^1.0.3", - "minizlib": "^2.1.2" + "minizlib": "^3.0.1" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" }, "optionalDependencies": { "encoding": "^0.1.13" @@ -3290,6 +3815,7 @@ "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", "dev": true, + "license": "ISC", "dependencies": { "minipass": "^3.0.0" }, @@ -3302,6 +3828,7 @@ "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", "dev": true, + "license": "ISC", "dependencies": { "yallist": "^4.0.0" }, @@ -3309,33 +3836,19 @@ "node": ">=8" } }, - "node_modules/minipass-json-stream": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minipass-json-stream/-/minipass-json-stream-1.0.1.tgz", - "integrity": "sha512-ODqY18UZt/I8k+b7rl2AENgbWE8IDYam+undIJONvigAz8KR5GWblsFTEfQs0WODsjbSXWlm+JHEv8Gr6Tfdbg==", - "dev": true, - "dependencies": { - "jsonparse": "^1.3.1", - "minipass": "^3.0.0" - } - }, - "node_modules/minipass-json-stream/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "node_modules/minipass-flush/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } + "license": "ISC" }, "node_modules/minipass-pipeline": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", "dev": true, + "license": "ISC", "dependencies": { "minipass": "^3.0.0" }, @@ -3348,6 +3861,7 @@ "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", "dev": true, + "license": "ISC", "dependencies": { "yallist": "^4.0.0" }, @@ -3355,11 +3869,19 @@ "node": ">=8" } }, + "node_modules/minipass-pipeline/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, "node_modules/minipass-sized": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", "dev": true, + "license": "ISC", "dependencies": { "minipass": "^3.0.0" }, @@ -3372,6 +3894,7 @@ "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", "dev": true, + "license": "ISC", "dependencies": { "yallist": "^4.0.0" }, @@ -3379,29 +3902,24 @@ "node": ">=8" } }, - "node_modules/minizlib": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", - "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "node_modules/minipass-sized/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true, - "dependencies": { - "minipass": "^3.0.0", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">= 8" - } + "license": "ISC" }, - "node_modules/minizlib/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "node_modules/minizlib": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", + "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", "dev": true, + "license": "MIT", "dependencies": { - "yallist": "^4.0.0" + "minipass": "^7.1.2" }, "engines": { - "node": ">=8" + "node": ">= 18" } }, "node_modules/mkdirp": { @@ -3409,6 +3927,7 @@ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", "dev": true, + "license": "MIT", "bin": { "mkdirp": "dist/cjs/src/bin.js" }, @@ -3420,14 +3939,16 @@ } }, "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" }, "node_modules/msgpackr": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.10.2.tgz", - "integrity": "sha512-L60rsPynBvNE+8BWipKKZ9jHcSGbtyJYIwjRq0VrIvQ08cRjntGXJYW/tmciZ2IHWIY8WEW32Qa2xbh5+SKBZA==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.11.1.tgz", + "integrity": "sha512-P6RWMjddZDjSndqKd5XhmHhpnxohGEj4+55w9tSACji7X/P6gTP/KzYGj7/U2RfE2D3HONS6Y63eGkuFXJEMtA==", + "license": "MIT", "optionalDependencies": { "msgpackr-extract": "^3.0.2" } @@ -3459,36 +3980,38 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==" }, "node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/node-gyp": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-10.1.0.tgz", - "integrity": "sha512-B4J5M1cABxPc5PwfjhbV5hoy2DP9p8lFXASnEN6hugXOa61416tnTZ29x9sSwAd0o99XNIcpvDDy1swAExsVKA==", + "version": "11.5.0", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-11.5.0.tgz", + "integrity": "sha512-ra7Kvlhxn5V9Slyus0ygMa2h+UqExPqUIkfk7Pc8QTLT956JLSy51uWFwHtIYy0vI8cB4BDhc/S03+880My/LQ==", "dev": true, + "license": "MIT", "dependencies": { "env-paths": "^2.2.0", "exponential-backoff": "^3.1.1", - "glob": "^10.3.10", "graceful-fs": "^4.2.6", - "make-fetch-happen": "^13.0.0", - "nopt": "^7.0.0", - "proc-log": "^3.0.0", + "make-fetch-happen": "^14.0.3", + "nopt": "^8.0.0", + "proc-log": "^5.0.0", "semver": "^7.3.5", - "tar": "^6.1.2", - "which": "^4.0.0" + "tar": "^7.4.3", + "tinyglobby": "^0.2.12", + "which": "^5.0.0" }, "bin": { "node-gyp": "bin/node-gyp.js" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/node-gyp-build-optional-packages": { @@ -3502,75 +4025,22 @@ "node-gyp-build-optional-packages-test": "build-test.js" } }, - "node_modules/node-gyp/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/node-gyp/node_modules/glob": { - "version": "10.4.1", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.1.tgz", - "integrity": "sha512-2jelhlq3E4ho74ZyVLN03oKdAZVUa6UDZzFLVH1H7dnoax+y9qyaq8zBkfDIggjniU19z0wU18y16jMB2eyVIw==", - "dev": true, - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "engines": { - "node": ">=16 || 14 >=14.18" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/node-gyp/node_modules/isexe": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", "dev": true, + "license": "ISC", "engines": { "node": ">=16" } }, - "node_modules/node-gyp/node_modules/minimatch": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", - "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/node-gyp/node_modules/proc-log": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-3.0.0.tgz", - "integrity": "sha512-++Vn7NS4Xf9NacaU9Xq3URUuqZETPsf8L4j5/ckhaRYsfPeRyzGw+iDjFhV/Jr3uNmTvvddEJFWh5R1gRgUH8A==", - "dev": true, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, "node_modules/node-gyp/node_modules/which": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", - "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", + "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", "dev": true, + "license": "ISC", "dependencies": { "isexe": "^3.1.1" }, @@ -3578,37 +4048,23 @@ "node-which": "bin/which.js" }, "engines": { - "node": "^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/nopt": { - "version": "7.2.1", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.1.tgz", - "integrity": "sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-8.1.0.tgz", + "integrity": "sha512-ieGu42u/Qsa4TFktmaKEwM6MQH0pOWnaB3htzh0JRtx84+Mebc0cbZYN5bC+6WTZ4+77xrL9Pn5m7CV6VIkV7A==", "dev": true, + "license": "ISC", "dependencies": { - "abbrev": "^2.0.0" + "abbrev": "^3.0.0" }, "bin": { "nopt": "bin/nopt.js" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/normalize-package-data": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-6.0.1.tgz", - "integrity": "sha512-6rvCfeRW+OEZagAB4lMLSNuTNYZWLVtKccK79VSTf//yTY5VOCgcpH80O+bZK8Neps7pUnd5G+QlMg1yV/2iZQ==", - "dev": true, - "dependencies": { - "hosted-git-info": "^7.0.0", - "is-core-module": "^2.8.1", - "semver": "^7.3.5", - "validate-npm-package-license": "^3.0.4" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/normalize-path": { @@ -3621,105 +4077,155 @@ } }, "node_modules/npm-bundled": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-3.0.1.tgz", - "integrity": "sha512-+AvaheE/ww1JEwRHOrn4WHNzOxGtVp+adrg2AeZS/7KuxGUYFuBta98wYpfHBbJp6Tg6j1NKSEVHNcfZzJHQwQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-4.0.0.tgz", + "integrity": "sha512-IxaQZDMsqfQ2Lz37VvyyEtKLe8FsRZuysmedy/N06TU1RyVppYKXrO4xIhR0F+7ubIBox6Q7nir6fQI3ej39iA==", "dev": true, + "license": "ISC", "dependencies": { - "npm-normalize-package-bin": "^3.0.0" + "npm-normalize-package-bin": "^4.0.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/npm-install-checks": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-6.3.0.tgz", - "integrity": "sha512-W29RiK/xtpCGqn6f3ixfRYGk+zRyr+Ew9F2E20BfXxT5/euLdA/Nm7fO7OeTGuAmTs30cpgInyJ0cYe708YTZw==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-7.1.2.tgz", + "integrity": "sha512-z9HJBCYw9Zr8BqXcllKIs5nI+QggAImbBdHphOzVYrz2CB4iQ6FzWyKmlqDZua+51nAu7FcemlbTc9VgQN5XDQ==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "semver": "^7.1.1" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/npm-normalize-package-bin": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-3.0.1.tgz", - "integrity": "sha512-dMxCf+zZ+3zeQZXKxmyuCKlIDPGuv8EF940xbkC4kQVDTtqoh6rJFO+JTKSA6/Rwi0getWmtuy4Itup0AMcaDQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-4.0.0.tgz", + "integrity": "sha512-TZKxPvItzai9kN9H/TkmCtx/ZN/hvr3vUycjlfmH0ootY9yFBzNOpiXAdIn1Iteqsvk4lQn6B5PTrt+n6h8k/w==", "dev": true, + "license": "ISC", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/npm-package-arg": { - "version": "11.0.2", - "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-11.0.2.tgz", - "integrity": "sha512-IGN0IAwmhDJwy13Wc8k+4PEbTPhpJnMtfR53ZbOyjkvmEcLS4nCwp6mvMWjS5sUjeiW3mpx6cHmuhKEu9XmcQw==", + "version": "13.0.1", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-13.0.1.tgz", + "integrity": "sha512-6zqls5xFvJbgFjB1B2U6yITtyGBjDBORB7suI4zA4T/sZ1OmkMFlaQSNB/4K0LtXNA1t4OprAFxPisadK5O2ag==", "dev": true, + "license": "ISC", "dependencies": { - "hosted-git-info": "^7.0.0", - "proc-log": "^4.0.0", + "hosted-git-info": "^9.0.0", + "proc-log": "^5.0.0", "semver": "^7.3.5", - "validate-npm-package-name": "^5.0.0" + "validate-npm-package-name": "^6.0.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm-packlist": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-8.0.2.tgz", - "integrity": "sha512-shYrPFIS/JLP4oQmAwDyk5HcyysKW8/JLTEA32S0Z5TzvpaeeX2yMFfoK1fjEBnCBvVyIB/Jj/GBFdm0wsgzbA==", + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-10.0.2.tgz", + "integrity": "sha512-DrIWNiWT0FTdDRjGOYfEEZUNe1IzaSZ+up7qBTKnrQDySpdmuOQvytrqQlpK5QrCA4IThMvL4wTumqaa1ZvVIQ==", "dev": true, + "license": "ISC", "dependencies": { - "ignore-walk": "^6.0.4" + "ignore-walk": "^8.0.0", + "proc-log": "^5.0.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm-pick-manifest": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-9.0.1.tgz", - "integrity": "sha512-Udm1f0l2nXb3wxDpKjfohwgdFUSV50UVwzEIpDXVsbDMXVIEF81a/i0UhuQbhrPMMmdiq3+YMFLFIRVLs3hxQw==", + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-11.0.1.tgz", + "integrity": "sha512-HnU7FYSWbo7dTVHtK0G+BXbZ0aIfxz/aUCVLN0979Ec6rGUX5cJ6RbgVx5fqb5G31ufz+BVFA7y1SkRTPVNoVQ==", "dev": true, + "license": "ISC", "dependencies": { - "npm-install-checks": "^6.0.0", - "npm-normalize-package-bin": "^3.0.0", - "npm-package-arg": "^11.0.0", + "npm-install-checks": "^7.1.0", + "npm-normalize-package-bin": "^4.0.0", + "npm-package-arg": "^13.0.0", "semver": "^7.3.5" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm-registry-fetch": { - "version": "16.2.1", - "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-16.2.1.tgz", - "integrity": "sha512-8l+7jxhim55S85fjiDGJ1rZXBWGtRLi1OSb4Z3BPLObPuIaeKRlPRiYMSHU4/81ck3t71Z+UwDDl47gcpmfQQA==", + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-19.0.0.tgz", + "integrity": "sha512-DFxSAemHUwT/POaXAOY4NJmEWBPB0oKbwD6FFDE9hnt1nORkt/FXvgjD4hQjoKoHw9u0Ezws9SPXwV7xE/Gyww==", "dev": true, + "license": "ISC", "dependencies": { - "@npmcli/redact": "^1.1.0", - "make-fetch-happen": "^13.0.0", + "@npmcli/redact": "^3.0.0", + "jsonparse": "^1.3.1", + "make-fetch-happen": "^15.0.0", "minipass": "^7.0.2", - "minipass-fetch": "^3.0.0", - "minipass-json-stream": "^1.0.1", - "minizlib": "^2.1.2", - "npm-package-arg": "^11.0.0", - "proc-log": "^4.0.0" + "minipass-fetch": "^4.0.0", + "minizlib": "^3.0.1", + "npm-package-arg": "^13.0.0", + "proc-log": "^5.0.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "node_modules/npm-registry-fetch/node_modules/@npmcli/agent": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-4.0.0.tgz", + "integrity": "sha512-kAQTcEN9E8ERLVg5AsGwLNoFb+oEG6engbqAU2P43gD4JEIkNGMHdVQ096FsOAAYpZPB0RSt0zgInKIAS1l5QA==", + "dev": true, + "license": "ISC", + "dependencies": { + "agent-base": "^7.1.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.1", + "lru-cache": "^11.2.1", + "socks-proxy-agent": "^8.0.3" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm-registry-fetch/node_modules/lru-cache": { + "version": "11.2.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.2.tgz", + "integrity": "sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/npm-registry-fetch/node_modules/make-fetch-happen": { + "version": "15.0.2", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-15.0.2.tgz", + "integrity": "sha512-sI1NY4lWlXBAfjmCtVWIIpBypbBdhHtcjnwnv+gtCnsaOffyFil3aidszGC8hgzJe+fT1qix05sWxmD/Bmf/oQ==", + "dev": true, + "license": "ISC", "dependencies": { - "wrappy": "1" + "@npmcli/agent": "^4.0.0", + "cacache": "^20.0.1", + "http-cache-semantics": "^4.1.1", + "minipass": "^7.0.2", + "minipass-fetch": "^4.0.0", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^1.0.0", + "proc-log": "^5.0.0", + "promise-retry": "^2.0.1", + "ssri": "^12.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/onetime": { @@ -3727,6 +4233,7 @@ "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", "dev": true, + "license": "MIT", "dependencies": { "mimic-fn": "^2.1.0" }, @@ -3742,21 +4249,23 @@ "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==", "dev": true, + "license": "(WTFPL OR MIT)", "bin": { "opener": "bin/opener-bin.js" } }, "node_modules/optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "license": "MIT", "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", - "word-wrap": "^1.2.3" + "word-wrap": "^1.2.5" }, "engines": { "node": ">= 0.8.0" @@ -3791,56 +4300,62 @@ } }, "node_modules/p-map": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", - "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.3.tgz", + "integrity": "sha512-VkndIv2fIB99swvQoA65bm+fsmt6UNdGeIB0oxBs+WhAhdh08QA04JXpI7rbB9r08/nkbysKoya9rtDERYOYMA==", "dev": true, - "dependencies": { - "aggregate-error": "^3.0.0" - }, + "license": "MIT", "engines": { - "node": ">=10" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, + "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==", + "dev": true, + "license": "BlueOak-1.0.0" + }, "node_modules/pacote": { - "version": "17.0.7", - "resolved": "https://registry.npmjs.org/pacote/-/pacote-17.0.7.tgz", - "integrity": "sha512-sgvnoUMlkv9xHwDUKjKQFXVyUi8dtJGKp3vg6sYy+TxbDic5RjZCHF3ygv0EJgNRZ2GfRONjlKPUfokJ9lDpwQ==", + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/pacote/-/pacote-21.0.3.tgz", + "integrity": "sha512-itdFlanxO0nmQv4ORsvA9K1wv40IPfB9OmWqfaJWvoJ30VKyHsqNgDVeG+TVhI7Gk7XW8slUy7cA9r6dF5qohw==", "dev": true, + "license": "ISC", "dependencies": { - "@npmcli/git": "^5.0.0", - "@npmcli/installed-package-contents": "^2.0.1", - "@npmcli/promise-spawn": "^7.0.0", - "@npmcli/run-script": "^7.0.0", - "cacache": "^18.0.0", + "@npmcli/git": "^7.0.0", + "@npmcli/installed-package-contents": "^3.0.0", + "@npmcli/package-json": "^7.0.0", + "@npmcli/promise-spawn": "^8.0.0", + "@npmcli/run-script": "^10.0.0", + "cacache": "^20.0.0", "fs-minipass": "^3.0.0", "minipass": "^7.0.2", - "npm-package-arg": "^11.0.0", - "npm-packlist": "^8.0.0", - "npm-pick-manifest": "^9.0.0", - "npm-registry-fetch": "^16.0.0", - "proc-log": "^4.0.0", + "npm-package-arg": "^13.0.0", + "npm-packlist": "^10.0.1", + "npm-pick-manifest": "^11.0.1", + "npm-registry-fetch": "^19.0.0", + "proc-log": "^5.0.0", "promise-retry": "^2.0.1", - "read-package-json": "^7.0.0", - "read-package-json-fast": "^3.0.0", - "sigstore": "^2.2.0", - "ssri": "^10.0.0", - "tar": "^6.1.11" + "sigstore": "^4.0.0", + "ssri": "^12.0.0", + "tar": "^7.4.3" }, "bin": { - "pacote": "lib/bin.js" + "pacote": "bin/index.js" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "license": "MIT", "dependencies": { "callsites": "^3.0.0" }, @@ -3853,6 +4368,7 @@ "resolved": "https://registry.npmjs.org/patch-console/-/patch-console-2.0.0.tgz", "integrity": "sha512-0YNdUceMdaQwoKce1gatDScmMo5pu/tfABfnzEqeG0gtTmd7mh/WcwgUjtAeOU7N8nFFlbQBnFK2gXW5fGvmMA==", "dev": true, + "license": "MIT", "engines": { "node": "^12.20.0 || ^14.13.1 || >=16.0.0" } @@ -3865,14 +4381,6 @@ "node": ">=8" } }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -3909,19 +4417,21 @@ } }, "node_modules/pirates": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", - "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", "dev": true, + "license": "MIT", "engines": { "node": ">= 6" } }, "node_modules/polite-json": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/polite-json/-/polite-json-4.0.1.tgz", - "integrity": "sha512-8LI5ZeCPBEb4uBbcYKNVwk4jgqNx1yHReWoW4H4uUihWlSqZsUDfSITrRhjliuPgxsNPFhNSudGO2Zu4cbWinQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/polite-json/-/polite-json-5.0.0.tgz", + "integrity": "sha512-OLS/0XeUAcE8a2fdwemNja+udKgXNnY6yKVIXqAD2zVRx1KvY6Ato/rZ2vdzbxqYwPW0u6SCNC/bAMPNzpzxbw==", "dev": true, + "license": "MIT", "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" }, @@ -3933,15 +4443,17 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "license": "MIT", "engines": { "node": ">= 0.8.0" } }, "node_modules/prismjs": { - "version": "1.29.0", - "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.29.0.tgz", - "integrity": "sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==", + "version": "1.30.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz", + "integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -3951,6 +4463,7 @@ "resolved": "https://registry.npmjs.org/prismjs-terminal/-/prismjs-terminal-1.2.3.tgz", "integrity": "sha512-xc0zuJ5FMqvW+DpiRkvxURlz98DdfDsZcFHdO699+oL+ykbFfgI7O4VDEgUyc07BSL2NHl3zdb8m/tZ/aaqUrw==", "dev": true, + "license": "BlueOak-1.0.0", "dependencies": { "chalk": "^5.2.0", "prismjs": "^1.29.0", @@ -3964,10 +4477,11 @@ } }, "node_modules/prismjs-terminal/node_modules/chalk": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", - "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", "dev": true, + "license": "MIT", "engines": { "node": "^12.17.0 || ^14.13 || >=16.0.0" }, @@ -3976,19 +4490,21 @@ } }, "node_modules/proc-log": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-4.2.0.tgz", - "integrity": "sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-5.0.0.tgz", + "integrity": "sha512-Azwzvl90HaF0aCz1JrDdXQykFakSSNPaPoiZ9fm5qJIMHioDZEi7OAdRwSm6rSoPtY3Qutnm3L7ogmg3dc+wbQ==", "dev": true, + "license": "ISC", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/process-on-spawn": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/process-on-spawn/-/process-on-spawn-1.0.0.tgz", - "integrity": "sha512-1WsPDsUSMmZH5LeMLegqkPDrsGgsWwk1Exipy2hvB0o/F0ASzbpIctSCcZIK1ykJvtTJULEH+20WOFjMvGnCTg==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/process-on-spawn/-/process-on-spawn-1.1.0.tgz", + "integrity": "sha512-JOnOPQ/8TZgjs1JIH/m9ni7FfimjNa/PRx7y/Wb5qdItsnhO0jE4AT7fC0HjC28DUQWDr50dwSYZLdRMlqDq3Q==", "dev": true, + "license": "MIT", "dependencies": { "fromentries": "^1.2.0" }, @@ -3996,17 +4512,12 @@ "node": ">=8" } }, - "node_modules/promise-inflight": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", - "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", - "dev": true - }, "node_modules/promise-retry": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", "dev": true, + "license": "MIT", "dependencies": { "err-code": "^2.0.2", "retry": "^0.12.0" @@ -4016,40 +4527,14 @@ } }, "node_modules/punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "license": "MIT", "engines": { "node": ">=6" } }, - "node_modules/queue": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/queue/-/queue-6.0.2.tgz", - "integrity": "sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==", - "dependencies": { - "inherits": "~2.0.3" - } - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "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/react": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", @@ -4102,6 +4587,7 @@ "resolved": "https://registry.npmjs.org/react-reconciler/-/react-reconciler-0.29.2.tgz", "integrity": "sha512-zZQqIiYgDCTP/f1N/mAR10nJGrPD2ZR+jDSEsKWJHYC7Cm2wodlwbR3upZRdC3cjIjSlTLNVyO7Iu0Yy7t2AYg==", "dev": true, + "license": "MIT", "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" @@ -4113,81 +4599,6 @@ "react": "^18.3.1" } }, - "node_modules/read-package-json": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/read-package-json/-/read-package-json-7.0.1.tgz", - "integrity": "sha512-8PcDiZ8DXUjLf687Ol4BR8Bpm2umR7vhoZOzNRt+uxD9GpBh/K+CAAALVIiYFknmvlmyg7hM7BSNUXPaCCqd0Q==", - "deprecated": "This package is no longer supported. Please use @npmcli/package-json instead.", - "dev": true, - "dependencies": { - "glob": "^10.2.2", - "json-parse-even-better-errors": "^3.0.0", - "normalize-package-data": "^6.0.0", - "npm-normalize-package-bin": "^3.0.0" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "node_modules/read-package-json-fast": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/read-package-json-fast/-/read-package-json-fast-3.0.2.tgz", - "integrity": "sha512-0J+Msgym3vrLOUB3hzQCuZHII0xkNGCtz/HJH9xZshwv9DbDwkw1KaE3gx/e2J5rpEY5rtOy6cyhKOPrkP7FZw==", - "dev": true, - "dependencies": { - "json-parse-even-better-errors": "^3.0.0", - "npm-normalize-package-bin": "^3.0.0" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/read-package-json/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/read-package-json/node_modules/glob": { - "version": "10.4.1", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.1.tgz", - "integrity": "sha512-2jelhlq3E4ho74ZyVLN03oKdAZVUa6UDZzFLVH1H7dnoax+y9qyaq8zBkfDIggjniU19z0wU18y16jMB2eyVIw==", - "dev": true, - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "engines": { - "node": ">=16 || 14 >=14.18" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/read-package-json/node_modules/minimatch": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", - "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -4205,6 +4616,7 @@ "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -4213,67 +4625,23 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "license": "MIT", "engines": { "node": ">=4" } }, "node_modules/resolve-import": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/resolve-import/-/resolve-import-1.4.5.tgz", - "integrity": "sha512-HXb4YqODuuXT7Icq1Z++0g2JmhgbUHSs3VT2xR83gqvAPUikYT2Xk+562KHQgiaNkbBOlPddYrDLsC44qQggzw==", - "dev": true, - "dependencies": { - "glob": "^10.3.3", - "walk-up-path": "^3.0.1" - }, - "engines": { - "node": "16 >=16.17.0 || 18 >= 18.6.0 || >=20" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/resolve-import/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/resolve-import/node_modules/glob": { - "version": "10.4.1", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.1.tgz", - "integrity": "sha512-2jelhlq3E4ho74ZyVLN03oKdAZVUa6UDZzFLVH1H7dnoax+y9qyaq8zBkfDIggjniU19z0wU18y16jMB2eyVIw==", - "dev": true, - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "engines": { - "node": ">=16 || 14 >=14.18" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/resolve-import/node_modules/minimatch": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", - "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/resolve-import/-/resolve-import-2.0.0.tgz", + "integrity": "sha512-jpKjLibLuc8D1XEV2+7zb0aqN7I8d12u89g/v6IsgCzdVlccMQJq4TKkPw5fbhHdxhm7nbVtN+KvOTnjFf+nEA==", "dev": true, + "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^2.0.1" + "glob": "^11.0.0", + "walk-up-path": "^4.0.0" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -4284,6 +4652,7 @@ "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz", "integrity": "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==", "dev": true, + "license": "MIT", "dependencies": { "onetime": "^5.1.0", "signal-exit": "^3.0.2" @@ -4299,26 +4668,19 @@ "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/retry": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", "dev": true, + "license": "MIT", "engines": { "node": ">= 4" } }, - "node_modules/reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, "node_modules/rimraf": { "version": "5.0.7", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.7.tgz", @@ -4338,9 +4700,9 @@ } }, "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==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" @@ -4383,33 +4745,12 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "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": { - "queue-microtask": "^1.2.2" - } - }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "dev": true, + "license": "MIT", "optional": true }, "node_modules/scheduler": { @@ -4422,10 +4763,11 @@ } }, "node_modules/semver": { - "version": "7.6.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", - "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", "dev": true, + "license": "ISC", "bin": { "semver": "bin/semver.js" }, @@ -4464,43 +4806,46 @@ } }, "node_modules/sigstore": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/sigstore/-/sigstore-2.3.1.tgz", - "integrity": "sha512-8G+/XDU8wNsJOQS5ysDVO0Etg9/2uA5gR9l4ZwijjlwxBcrU6RPfwi2+jJmbP+Ap1Hlp/nVAaEO4Fj22/SL2gQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/sigstore/-/sigstore-4.0.0.tgz", + "integrity": "sha512-Gw/FgHtrLM9WP8P5lLcSGh9OQcrTruWCELAiS48ik1QbL0cH+dfjomiRTUE9zzz+D1N6rOLkwXUvVmXZAsNE0Q==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@sigstore/bundle": "^2.3.2", - "@sigstore/core": "^1.0.0", - "@sigstore/protobuf-specs": "^0.3.2", - "@sigstore/sign": "^2.3.2", - "@sigstore/tuf": "^2.3.4", - "@sigstore/verify": "^1.2.1" + "@sigstore/bundle": "^4.0.0", + "@sigstore/core": "^3.0.0", + "@sigstore/protobuf-specs": "^0.5.0", + "@sigstore/sign": "^4.0.0", + "@sigstore/tuf": "^4.0.0", + "@sigstore/verify": "^3.0.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/slice-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-6.0.0.tgz", - "integrity": "sha512-6bn4hRfkTvDfUoEQYkERg0BVF1D0vrX9HEkMl08uDiNWvVvjylLHvZFZWkDo6wjT8tUctbYl1nCOuE66ZTaUtA==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.2.tgz", + "integrity": "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^6.2.1", - "is-fullwidth-code-point": "^4.0.0" + "is-fullwidth-code-point": "^5.0.0" }, "engines": { - "node": ">=14.16" + "node": ">=18" }, "funding": { "url": "https://github.com/chalk/slice-ansi?sponsor=1" } }, "node_modules/slice-ansi/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==", + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", "dev": true, + "license": "MIT", "engines": { "node": ">=12" }, @@ -4508,23 +4853,41 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/slice-ansi/node_modules/is-fullwidth-code-point": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz", + "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.3.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/smart-buffer": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", "dev": true, + "license": "MIT", "engines": { "node": ">= 6.0.0", "npm": ">= 3.0.0" } }, "node_modules/socks": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.3.tgz", - "integrity": "sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==", + "version": "2.8.7", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz", + "integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==", "dev": true, + "license": "MIT", "dependencies": { - "ip-address": "^9.0.5", + "ip-address": "^10.0.1", "smart-buffer": "^4.2.0" }, "engines": { @@ -4533,14 +4896,15 @@ } }, "node_modules/socks-proxy-agent": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.3.tgz", - "integrity": "sha512-VNegTZKhuGq5vSD6XNKlbqWhyt/40CgoEw8XxD6dhnm8Jq9IEa3nIa4HwnM8XOqU0CdB0BwWVXusqiFXfHB3+A==", + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", + "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", "dev": true, + "license": "MIT", "dependencies": { - "agent-base": "^7.1.1", + "agent-base": "^7.1.2", "debug": "^4.3.4", - "socks": "^2.7.1" + "socks": "^2.8.3" }, "engines": { "node": ">= 14" @@ -4551,6 +4915,7 @@ "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", "dev": true, + "license": "Apache-2.0", "dependencies": { "spdx-expression-parse": "^3.0.0", "spdx-license-ids": "^3.0.0" @@ -4560,40 +4925,38 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", - "dev": true + "dev": true, + "license": "CC-BY-3.0" }, "node_modules/spdx-expression-parse": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", "dev": true, + "license": "MIT", "dependencies": { "spdx-exceptions": "^2.1.0", "spdx-license-ids": "^3.0.0" } }, "node_modules/spdx-license-ids": { - "version": "3.0.18", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.18.tgz", - "integrity": "sha512-xxRs31BqRYHwiMzudOrpSiHtZ8i/GeionCBDSilhYRj+9gIcI8wCZTlXZKu9vZIVqViP3dcp9qE5G6AlIaD+TQ==", - "dev": true - }, - "node_modules/sprintf-js": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", - "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", - "dev": true + "version": "3.0.22", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.22.tgz", + "integrity": "sha512-4PRT4nh1EImPbt2jASOKHX7PB7I+e4IWNLvkKFDxNhJlfjbYlleYQh285Z/3mPTHSAK/AvdMmw5BNNuYH8ShgQ==", + "dev": true, + "license": "CC0-1.0" }, "node_modules/ssri": { - "version": "10.0.6", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-10.0.6.tgz", - "integrity": "sha512-MGrFH9Z4NP9Iyhqn16sDtBpRRNJ0Y2hNa6D65h736fVSaPCHr4DM4sWUNvVaSuC+0OBGhwsrydQwmgfg5LncqQ==", + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-12.0.0.tgz", + "integrity": "sha512-S7iGNosepx9RadX82oimUkvr0Ct7IjJbEbs4mJcTxst8um95J3sDYU1RBEOvdu6oL1Wek2ODI5i4MAw+dZ6cAQ==", "dev": true, + "license": "ISC", "dependencies": { "minipass": "^7.0.3" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/stack-utils": { @@ -4601,6 +4964,7 @@ "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", "dev": true, + "license": "MIT", "dependencies": { "escape-string-regexp": "^2.0.0" }, @@ -4613,6 +4977,7 @@ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -4622,6 +4987,7 @@ "resolved": "https://registry.npmjs.org/string-length/-/string-length-6.0.0.tgz", "integrity": "sha512-1U361pxZHEQ+FeSjzqRpV+cu2vTzYeWeafXFLykiFlv4Vc0n3njgU8HrMbyik5uwm77naWMuVG8fhEF+Ovb1Kg==", "dev": true, + "license": "MIT", "dependencies": { "strip-ansi": "^7.1.0" }, @@ -4633,10 +4999,11 @@ } }, "node_modules/string-length/node_modules/ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", "dev": true, + "license": "MIT", "engines": { "node": ">=12" }, @@ -4645,10 +5012,11 @@ } }, "node_modules/string-length/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==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", "dev": true, + "license": "MIT", "dependencies": { "ansi-regex": "^6.0.1" }, @@ -4754,6 +5122,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "license": "MIT", "engines": { "node": ">=8" }, @@ -4778,230 +5147,283 @@ } }, "node_modules/sync-content": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/sync-content/-/sync-content-1.0.2.tgz", - "integrity": "sha512-znd3rYiiSxU3WteWyS9a6FXkTA/Wjk8WQsOyzHbineeL837dLn3DA4MRhsIX3qGcxDMH6+uuFV4axztssk7wEQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/sync-content/-/sync-content-2.0.1.tgz", + "integrity": "sha512-NI1mo514yFhr8pV/5Etvgh+pSBUIpoAKoiBIUwALVlQQNAwb40bTw8hhPFaip/dvv0GhpHVOq0vq8iY02ppLTg==", "dev": true, + "license": "BlueOak-1.0.0", "dependencies": { - "glob": "^10.2.6", + "glob": "^11.0.0", "mkdirp": "^3.0.1", - "path-scurry": "^1.9.2", - "rimraf": "^5.0.1" + "path-scurry": "^2.0.0", + "rimraf": "^6.0.0", + "tshy": "^3.0.0" }, "bin": { - "sync-content": "dist/mjs/bin.mjs" + "sync-content": "dist/esm/bin.mjs" }, "engines": { - "node": ">=14" + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/sync-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==", + "node_modules/sync-content/node_modules/lru-cache": { + "version": "11.2.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.2.tgz", + "integrity": "sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==", "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" + "license": "ISC", + "engines": { + "node": "20 || >=22" } }, - "node_modules/sync-content/node_modules/glob": { - "version": "10.4.1", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.1.tgz", - "integrity": "sha512-2jelhlq3E4ho74ZyVLN03oKdAZVUa6UDZzFLVH1H7dnoax+y9qyaq8zBkfDIggjniU19z0wU18y16jMB2eyVIw==", + "node_modules/sync-content/node_modules/path-scurry": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", + "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==", "dev": true, + "license": "BlueOak-1.0.0", "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" + "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/sync-content/node_modules/minimatch": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", - "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", + "node_modules/sync-content/node_modules/rimraf": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-6.0.1.tgz", + "integrity": "sha512-9dkvaxAsk/xNXSJzMgFqqMCuFgt2+KsOFek3TMLfo8NCPfWpBmqwyNn5Y+NX56QUYfCtsyhF3ayiboEoUmJk/A==", "dev": true, + "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.1" + "glob": "^11.0.0", + "package-json-from-dist": "^1.0.0" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/tap": { - "version": "19.0.2", - "resolved": "https://registry.npmjs.org/tap/-/tap-19.0.2.tgz", - "integrity": "sha512-SRGulk1RKlVuYtnPeephj+xyE0sG9CvGlKYP4lymBZykLtkwBPnEBjQ2iQmLX5z0BFEMfKh8G4bvZkhoSJb3kg==", - "dev": true, - "dependencies": { - "@tapjs/after": "1.1.24", - "@tapjs/after-each": "2.0.1", - "@tapjs/asserts": "2.0.1", - "@tapjs/before": "2.0.1", - "@tapjs/before-each": "2.0.1", - "@tapjs/core": "2.0.1", - "@tapjs/filter": "2.0.1", - "@tapjs/fixture": "2.0.1", - "@tapjs/intercept": "2.0.1", - "@tapjs/mock": "2.0.1", - "@tapjs/node-serialize": "2.0.1", - "@tapjs/run": "2.0.2", - "@tapjs/snapshot": "2.0.1", - "@tapjs/spawn": "2.0.1", - "@tapjs/stdin": "2.0.1", - "@tapjs/test": "2.0.1", - "@tapjs/typescript": "1.4.6", - "@tapjs/worker": "2.0.1", - "resolve-import": "^1.4.5" + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/tap/-/tap-21.1.1.tgz", + "integrity": "sha512-WQQkoJw2LbusXPq9d6A3N4SHCpiog1AbjSVyNlqRh6uiCrtra24bZRCURX8cgBjKV4W22dcRevvhMp24+N/oVg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@tapjs/after": "3.0.2", + "@tapjs/after-each": "4.0.2", + "@tapjs/asserts": "4.0.2", + "@tapjs/before": "4.0.2", + "@tapjs/before-each": "4.0.2", + "@tapjs/chdir": "3.0.2", + "@tapjs/core": "4.0.2", + "@tapjs/filter": "4.0.2", + "@tapjs/fixture": "4.0.2", + "@tapjs/intercept": "4.0.2", + "@tapjs/mock": "4.0.2", + "@tapjs/node-serialize": "4.0.2", + "@tapjs/run": "4.0.3", + "@tapjs/snapshot": "4.0.2", + "@tapjs/spawn": "4.0.2", + "@tapjs/stdin": "4.0.2", + "@tapjs/test": "4.0.2", + "@tapjs/typescript": "3.1.1", + "@tapjs/worker": "4.0.2", + "resolve-import": "2" }, "bin": { "tap": "dist/esm/run.mjs" }, "engines": { - "node": "16 >=16.17.0 || 18 >= 18.6.0 || >=20" + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/tap-parser": { - "version": "16.0.1", - "resolved": "https://registry.npmjs.org/tap-parser/-/tap-parser-16.0.1.tgz", - "integrity": "sha512-vKianJzSSzLkJ3bHBwzvZDDRi9yGMwkRANJxwPAjAue50owB8rlluYySmTN4tZVH0nsh6stvrQbg9kuCL5svdg==", + "version": "18.0.1", + "resolved": "https://registry.npmjs.org/tap-parser/-/tap-parser-18.0.1.tgz", + "integrity": "sha512-Jcu51jzPCDXzuFvyCDGRfJWm2mJ0yRLf+aqWINk4P03HM9uyrq2SLavn26N8qwVz//GZysNt3DQNvKrYjidB3g==", "dev": true, + "license": "BlueOak-1.0.0", "dependencies": { "events-to-array": "^2.0.3", - "tap-yaml": "2.2.2" + "tap-yaml": "4.0.1" }, "bin": { "tap-parser": "bin/cmd.cjs" }, "engines": { - "node": "16 >=16.17.0 || 18 >= 18.6.0 || >=20" + "node": "20 || >=22" } }, "node_modules/tap-yaml": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/tap-yaml/-/tap-yaml-2.2.2.tgz", - "integrity": "sha512-MWG4OpAKtNoNVjCz/BqlDJiwTM99tiHRhHPS4iGOe1ZS0CgM4jSFH92lthSFvvy4EdDjQZDV7uYqUFlU9JuNhw==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/tap-yaml/-/tap-yaml-4.0.1.tgz", + "integrity": "sha512-2D5FSjxSP8v3LCh3N1KZa+FoIaVh2f7bFaUNd4mm74Kx/a5JqKu6QspEQZpAwH1Ez2SlbguOMWKHPTels2xbzA==", "dev": true, + "license": "BlueOak-1.0.0", "dependencies": { - "yaml": "^2.4.1", - "yaml-types": "^0.3.0" + "yaml": "^2.8.1", + "yaml-types": "^0.4.0" }, "engines": { - "node": "16 >=16.17.0 || 18 >= 18.6.0 || >=20" + "node": "20 || >=22" } }, "node_modules/tar": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", - "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.1.tgz", + "integrity": "sha512-nlGpxf+hv0v7GkWBK2V9spgactGOp0qvfWRxUMjqHyzrt3SgwE48DIv/FhqPHJYLHpgW1opq3nERbz5Anq7n1g==", "dev": true, + "license": "ISC", "dependencies": { - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "minipass": "^5.0.0", - "minizlib": "^2.1.1", - "mkdirp": "^1.0.3", - "yallist": "^4.0.0" + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.1.0", + "yallist": "^5.0.0" }, "engines": { - "node": ">=10" + "node": ">=18" } }, - "node_modules/tar/node_modules/fs-minipass": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", - "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "node_modules/tcompare": { + "version": "6.4.5", + "resolved": "https://registry.npmjs.org/tcompare/-/tcompare-6.4.5.tgz", + "integrity": "sha512-Whuz9xlKKI2XXICKDSDRKjXdBuC6gBNOgmEUtH7UFyQeYzfUMQ19DyjZULarGKDGFhgOg3CJ+IQUEfpkOPg0Uw==", "dev": true, "dependencies": { - "minipass": "^3.0.0" + "diff": "^5.1.0", + "react-element-to-jsx-string": "^15.0.0" }, "engines": { - "node": ">= 8" + "node": "16 >=16.17.0 || 18 >= 18.6.0 || >=20" } }, - "node_modules/tar/node_modules/fs-minipass/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "node_modules/test-exclude": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.1.tgz", + "integrity": "sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==", "dev": true, + "license": "ISC", "dependencies": { - "yallist": "^4.0.0" + "@istanbuljs/schema": "^0.1.2", + "glob": "^10.4.1", + "minimatch": "^9.0.4" }, "engines": { - "node": ">=8" + "node": ">=18" } }, - "node_modules/tar/node_modules/minipass": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", - "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "node_modules/test-exclude/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, - "engines": { - "node": ">=8" + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" } }, - "node_modules/tar/node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "node_modules/test-exclude/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, "bin": { - "mkdirp": "bin/cmd.js" + "glob": "dist/esm/bin.mjs" }, - "engines": { - "node": ">=10" + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/tcompare": { - "version": "6.4.5", - "resolved": "https://registry.npmjs.org/tcompare/-/tcompare-6.4.5.tgz", - "integrity": "sha512-Whuz9xlKKI2XXICKDSDRKjXdBuC6gBNOgmEUtH7UFyQeYzfUMQ19DyjZULarGKDGFhgOg3CJ+IQUEfpkOPg0Uw==", + "node_modules/test-exclude/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "dev": true, + "license": "ISC", "dependencies": { - "diff": "^5.1.0", - "react-element-to-jsx-string": "^15.0.0" + "brace-expansion": "^2.0.1" }, "engines": { - "node": "16 >=16.17.0 || 18 >= 18.6.0 || >=20" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/test-exclude": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", - "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", "dev": true, + "license": "MIT", "dependencies": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" + "fdir": "^6.5.0", + "picomatch": "^4.0.3" }, "engines": { - "node": ">=8" + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" } }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==" + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } }, "node_modules/to-regex-range": { "version": "5.0.1", @@ -5021,49 +5443,43 @@ "resolved": "https://registry.npmjs.org/trivial-deferred/-/trivial-deferred-2.0.0.tgz", "integrity": "sha512-iGbM7X2slv9ORDVj2y2FFUq3cP/ypbtu2nQ8S38ufjL0glBABvmR9pTdsib1XtS2LUhhLMbelaBUaf/s5J3dSw==", "dev": true, + "license": "ISC", "engines": { "node": ">= 8" } }, "node_modules/tshy": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/tshy/-/tshy-1.14.0.tgz", - "integrity": "sha512-YiUujgi4Jb+t2I48LwSRzHkBpniH9WjjktNozn+nlsGmVemKSjDNY7EwBRPvPCr5zAC/3ITAYWH9Z7kUinGSrw==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/tshy/-/tshy-3.0.3.tgz", + "integrity": "sha512-bUX6HQCvVdPyPLy2VZuKw95CtYD5aRSEgYEK7IPV9l9xN/z284kl5/hIwOfLY/mZOOdhrO34dFOOcL1VUMVyaw==", "dev": true, + "license": "BlueOak-1.0.0", "dependencies": { - "chalk": "^5.3.0", - "chokidar": "^3.6.0", - "foreground-child": "^3.1.1", - "minimatch": "^9.0.4", + "chalk": "^5.6.2", + "chokidar": "^4.0.3", + "foreground-child": "^3.3.1", + "minimatch": "^10.0.3", "mkdirp": "^3.0.1", - "polite-json": "^4.0.1", - "resolve-import": "^1.4.5", - "rimraf": "^5.0.1", - "sync-content": "^1.0.2", - "typescript": "^5.4.5", - "walk-up-path": "^3.0.1" + "polite-json": "^5.0.0", + "resolve-import": "^2.0.0", + "rimraf": "^6.0.1", + "sync-content": "^2.0.1", + "typescript": "^5.9.3", + "walk-up-path": "^4.0.0" }, "bin": { "tshy": "dist/esm/index.js" }, "engines": { - "node": "16 >=16.17 || 18 >=18.15.0 || >=20.6.1" - } - }, - "node_modules/tshy/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" + "node": "20 || >=22" } }, "node_modules/tshy/node_modules/chalk": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", - "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", "dev": true, + "license": "MIT", "engines": { "node": "^12.17.0 || ^14.13 || >=16.0.0" }, @@ -5071,16 +5487,67 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/tshy/node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/tshy/node_modules/minimatch": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", - "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.3.tgz", + "integrity": "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==", "dev": true, + "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.1" + "@isaacs/brace-expansion": "^5.0.0" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/tshy/node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/tshy/node_modules/rimraf": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-6.0.1.tgz", + "integrity": "sha512-9dkvaxAsk/xNXSJzMgFqqMCuFgt2+KsOFek3TMLfo8NCPfWpBmqwyNn5Y+NX56QUYfCtsyhF3ayiboEoUmJk/A==", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^11.0.0", + "package-json-from-dist": "^1.0.0" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -5092,23 +5559,75 @@ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, "node_modules/tuf-js": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tuf-js/-/tuf-js-2.2.1.tgz", - "integrity": "sha512-GwIJau9XaA8nLVbUXsN3IlFi7WmQ48gBUrl3FTkkL/XLu/POhBzfmX9hd33FNMX1qAsfl6ozO1iMmW9NC8YniA==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/tuf-js/-/tuf-js-4.0.0.tgz", + "integrity": "sha512-Lq7ieeGvXDXwpoSmOSgLWVdsGGV9J4a77oDTAPe/Ltrqnnm/ETaRlBAQTH5JatEh8KXuE6sddf9qAv1Q2282Hg==", "dev": true, + "license": "MIT", "dependencies": { - "@tufjs/models": "2.0.1", - "debug": "^4.3.4", - "make-fetch-happen": "^13.0.1" + "@tufjs/models": "4.0.0", + "debug": "^4.4.1", + "make-fetch-happen": "^15.0.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/tuf-js/node_modules/@npmcli/agent": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-4.0.0.tgz", + "integrity": "sha512-kAQTcEN9E8ERLVg5AsGwLNoFb+oEG6engbqAU2P43gD4JEIkNGMHdVQ096FsOAAYpZPB0RSt0zgInKIAS1l5QA==", + "dev": true, + "license": "ISC", + "dependencies": { + "agent-base": "^7.1.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.1", + "lru-cache": "^11.2.1", + "socks-proxy-agent": "^8.0.3" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/tuf-js/node_modules/lru-cache": { + "version": "11.2.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.2.tgz", + "integrity": "sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/tuf-js/node_modules/make-fetch-happen": { + "version": "15.0.2", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-15.0.2.tgz", + "integrity": "sha512-sI1NY4lWlXBAfjmCtVWIIpBypbBdhHtcjnwnv+gtCnsaOffyFil3aidszGC8hgzJe+fT1qix05sWxmD/Bmf/oQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/agent": "^4.0.0", + "cacache": "^20.0.1", + "http-cache-semantics": "^4.1.1", + "minipass": "^7.0.2", + "minipass-fetch": "^4.0.0", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^1.0.0", + "proc-log": "^5.0.0", + "promise-retry": "^2.0.1", + "ssri": "^12.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "license": "MIT", "dependencies": { "prelude-ls": "^1.2.1" }, @@ -5117,21 +5636,24 @@ } }, "node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", "engines": { - "node": ">=10" + "node": ">=16" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/typescript": { - "version": "5.4.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", - "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, + "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -5141,40 +5663,44 @@ } }, "node_modules/undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "version": "7.14.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.14.0.tgz", + "integrity": "sha512-QQiYxHuyZ9gQUIrmPo3IA+hUl4KYk8uSA7cHrcKd/l3p1OTpZcM0Tbp9x7FAtXdAYhlasd60ncPpgu6ihG6TOA==", "dev": true, + "license": "MIT", "peer": true }, "node_modules/unique-filename": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-3.0.0.tgz", - "integrity": "sha512-afXhuC55wkAmZ0P18QsVE6kp8JaxrEokN2HGIoIVv2ijHQd419H0+6EigAFcIzXeMIkcIkNBpB3L/DXB3cTS/g==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-4.0.0.tgz", + "integrity": "sha512-XSnEewXmQ+veP7xX2dS5Q4yZAvO40cBN2MWkJ7D/6sW4Dg6wYBNwM1Vrnz1FhH5AdeLIlUXRI9e28z1YZi71NQ==", "dev": true, + "license": "ISC", "dependencies": { - "unique-slug": "^4.0.0" + "unique-slug": "^5.0.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/unique-slug": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-4.0.0.tgz", - "integrity": "sha512-WrcA6AyEfqDX5bWige/4NQfPZMtASNVxdmWR76WESYQVAACSgWcR6e9i0mofqqBxYFtL4oAxPIptY73/0YE1DQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-5.0.0.tgz", + "integrity": "sha512-9OdaqO5kwqR+1kVgHAhsp5vPNU0hnxRa26rBFNfNgM7M6pNtgzeBn3s/xbyCQL3dcjzOatcef6UUHpB/6MaETg==", "dev": true, + "license": "ISC", "dependencies": { "imurmurhash": "^0.1.4" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "license": "BSD-2-Clause", "dependencies": { "punycode": "^2.1.0" } @@ -5184,6 +5710,7 @@ "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", "dev": true, + "license": "MIT", "bin": { "uuid": "dist/bin/uuid" } @@ -5192,13 +5719,15 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/v8-to-istanbul": { - "version": "9.2.0", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.2.0.tgz", - "integrity": "sha512-/EH/sDgxU2eGxajKdwLCDmQ4FWq+kpi3uCmBGpw1xJtnAxEjlD8j8PEiGWpCIMIs3ciNAgH0d3TTJiUkYzyZjA==", + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", + "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", "dev": true, + "license": "ISC", "dependencies": { "@jridgewell/trace-mapping": "^0.3.12", "@types/istanbul-lib-coverage": "^2.0.1", @@ -5209,10 +5738,11 @@ } }, "node_modules/v8-to-istanbul/node_modules/@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", "dev": true, + "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" @@ -5223,25 +5753,31 @@ "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", "dev": true, + "license": "Apache-2.0", "dependencies": { "spdx-correct": "^3.0.0", "spdx-expression-parse": "^3.0.0" } }, "node_modules/validate-npm-package-name": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-5.0.1.tgz", - "integrity": "sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-6.0.2.tgz", + "integrity": "sha512-IUoow1YUtvoBBC06dXs8bR8B9vuA3aJfmQNKMoaPG/OFsPmoQvw8xh+6Ye25Gx9DQhoEom3Pcu9MKHerm/NpUQ==", "dev": true, + "license": "ISC", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/walk-up-path": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/walk-up-path/-/walk-up-path-3.0.1.tgz", - "integrity": "sha512-9YlCL/ynK3CTlrSRrDxZvUauLzAswPCrsaCgilqFevUYpeEW0/3ScEjaa3kbW/T0ghhkEr7mv+fpjqn1Y1YuTA==", - "dev": true + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/walk-up-path/-/walk-up-path-4.0.0.tgz", + "integrity": "sha512-3hu+tD8YzSLGuFYtPRb48vdhKMi0KQV5sn+uWr8+7dMEq/2G/dtLrdDinkLjqq5TIbIBjYJ4Ax/n3YiaW7QM8A==", + "dev": true, + "license": "ISC", + "engines": { + "node": "20 || >=22" + } }, "node_modules/which": { "version": "2.0.2", @@ -5258,20 +5794,81 @@ } }, "node_modules/widest-line": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-4.0.1.tgz", - "integrity": "sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-5.0.0.tgz", + "integrity": "sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA==", "dev": true, + "license": "MIT", "dependencies": { - "string-width": "^5.0.1" + "string-width": "^7.0.0" }, "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/widest-line/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { "node": ">=12" }, "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/widest-line/node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "dev": true, + "license": "MIT" + }, + "node_modules/widest-line/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/widest-line/node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/word-count": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/word-count/-/word-count-0.3.1.tgz", + "integrity": "sha512-tTDKyKX1yy8FZWUw64TOgnMeLeXPZBA+ZunaCCpFRrGFeLSwSdEgSsFI/5DIJkiUfM7CkCREFPkZd9U4eLXgqA==", + "license": "MIT" + }, "node_modules/word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", @@ -5375,15 +5972,10 @@ "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" - }, "node_modules/ws": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", - "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", "dev": true, "license": "MIT", "engines": { @@ -5407,33 +5999,40 @@ "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", "dev": true, + "license": "ISC", "engines": { "node": ">=10" } }, "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } }, "node_modules/yaml": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.2.tgz", - "integrity": "sha512-B3VqDZ+JAg1nZpaEmWtTXUlBneoGx6CPM9b0TENK6aoSu5t73dItudwdgmi6tHlIZZId4dZ9skcAQ2UbcyAeVA==", + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz", + "integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==", "dev": true, + "license": "ISC", "bin": { "yaml": "bin.mjs" }, "engines": { - "node": ">= 14" + "node": ">= 14.6" } }, "node_modules/yaml-types": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/yaml-types/-/yaml-types-0.3.0.tgz", - "integrity": "sha512-i9RxAO/LZBiE0NJUy9pbN5jFz5EasYDImzRkj8Y81kkInTi1laia3P3K/wlMKzOxFQutZip8TejvQP/DwgbU7A==", + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/yaml-types/-/yaml-types-0.4.0.tgz", + "integrity": "sha512-XfbA30NUg4/LWUiplMbiufUiwYhgB9jvBhTWel7XQqjV+GaB79c2tROu/8/Tu7jO0HvDvnKWtBk5ksWRrhQ/0g==", "dev": true, + "license": "ISC", "engines": { "node": ">= 16", "npm": ">= 7" @@ -5447,6 +6046,7 @@ "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", "dev": true, + "license": "MIT", "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", @@ -5465,6 +6065,7 @@ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", "dev": true, + "license": "ISC", "engines": { "node": ">=12" } @@ -5473,13 +6074,15 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/yargs/node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -5489,6 +6092,7 @@ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, + "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -5509,11 +6113,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/yoga-wasm-web": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/yoga-wasm-web/-/yoga-wasm-web-0.3.3.tgz", - "integrity": "sha512-N+d4UJSJbt/R3wqY7Coqs5pcV0aUj2j9IaQ3rNj9bVCLld8tTGKRa2USARjnvZJWVx1NDmQev8EknoczaOQDOA==", - "dev": true + "node_modules/yoga-layout": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/yoga-layout/-/yoga-layout-3.2.1.tgz", + "integrity": "sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ==", + "dev": true, + "license": "MIT" } }, "dependencies": { @@ -5528,9 +6133,9 @@ }, "dependencies": { "ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", "dev": true } } @@ -5542,9 +6147,9 @@ "dev": true }, "@bcoe/v8-coverage": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", - "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", + "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", "dev": true }, "@cspotcode/source-map-support": { @@ -5557,27 +6162,60 @@ } }, "@eslint-community/eslint-utils": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", - "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", + "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", "requires": { - "eslint-visitor-keys": "^3.3.0" + "eslint-visitor-keys": "^3.4.3" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==" + } } }, "@eslint-community/regexpp": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.5.0.tgz", - "integrity": "sha512-vITaYzIcNmjn5tF5uxcZ/ft7/RXGrMUIS9HalWckEOF6ESiwXKoMzAQf2UW0aVd6rnOeExTJVd5hmWXucBKGXQ==" + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==" + }, + "@eslint/config-array": { + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz", + "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==", + "requires": { + "@eslint/object-schema": "^2.1.6", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + } + }, + "@eslint/config-helpers": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.0.tgz", + "integrity": "sha512-WUFvV4WoIwW8Bv0KeKCIIEgdSiFOsulyN0xrMu+7z43q/hkOLXjvb5u7UC9jDxvRzcrbEmuZBX5yJZz1741jog==", + "requires": { + "@eslint/core": "^0.16.0" + } + }, + "@eslint/core": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.16.0.tgz", + "integrity": "sha512-nmC8/totwobIiFcGkDza3GIKfAw1+hLiYVrh3I1nIomQ8PEr5cxg34jnkmGawul/ep52wGRAcyeDCNtWKSOj4Q==", + "requires": { + "@types/json-schema": "^7.0.15" + } }, "@eslint/eslintrc": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.2.tgz", - "integrity": "sha512-3W4f5tDUra+pA+FzgugqL2pRimUTDJWKr7BINqOpkZrC0uYI0NIc0/JFgBROCU07HR6GieA5m3/rsPIhDmCXTQ==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", + "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", "requires": { "ajv": "^6.12.4", "debug": "^4.3.2", - "espree": "^9.5.1", - "globals": "^13.19.0", + "espree": "^10.0.1", + "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", @@ -5586,18 +6224,43 @@ } }, "@eslint/js": { - "version": "8.37.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.37.0.tgz", - "integrity": "sha512-x5vzdtOOGgFVDCUs81QRB2+liax8rFg3+7hqM+QhBG0/G3F1ZsoYl97UrqgHgQ9KKT7G6c4V+aTUCgu/n22v1A==" + "version": "9.37.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.37.0.tgz", + "integrity": "sha512-jaS+NJ+hximswBG6pjNX0uEJZkrT0zwpVi3BA3vX22aFGjJjmgSTSmPpZCRKmoBL5VY/M6p0xsSJx7rk7sy5gg==" }, - "@humanwhocodes/config-array": { - "version": "0.11.8", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", - "integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==", + "@eslint/object-schema": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", + "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==" + }, + "@eslint/plugin-kit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.0.tgz", + "integrity": "sha512-sB5uyeq+dwCWyPi31B2gQlVlo+j5brPlWx4yZBrEaRo/nhdDE8Xke1gsGgtiBdaBTxuTkceLVuVt/pclrasb0A==", "requires": { - "@humanwhocodes/object-schema": "^1.2.1", - "debug": "^4.1.1", - "minimatch": "^3.0.5" + "@eslint/core": "^0.16.0", + "levn": "^0.4.1" + } + }, + "@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==" + }, + "@humanfs/node": { + "version": "0.16.6", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", + "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", + "requires": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.3.0" + }, + "dependencies": { + "@humanwhocodes/retry": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", + "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==" + } } }, "@humanwhocodes/module-importer": { @@ -5605,10 +6268,25 @@ "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==" }, - "@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==" + "@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==" + }, + "@isaacs/balanced-match": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", + "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==", + "dev": true + }, + "@isaacs/brace-expansion": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz", + "integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==", + "dev": true, + "requires": { + "@isaacs/balanced-match": "^4.0.1" + } }, "@isaacs/cliui": { "version": "8.0.2", @@ -5638,6 +6316,15 @@ } } }, + "@isaacs/fs-minipass": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "dev": true, + "requires": { + "minipass": "^7.0.4" + } + }, "@isaacs/ts-node-temp-fork-for-pr-2009": { "version": "10.9.7", "resolved": "https://registry.npmjs.org/@isaacs/ts-node-temp-fork-for-pr-2009/-/ts-node-temp-fork-for-pr-2009-10.9.7.tgz", @@ -5678,9 +6365,9 @@ "dev": true }, "@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", "dev": true }, "@jridgewell/trace-mapping": { @@ -5738,33 +6425,10 @@ "integrity": "sha512-O+6Gs8UeDbyFpbSh2CPEz/UOrrdWPTBYNblZK5CxxLisYt4kGX3Sc+czffFonyjiGSq3jWLwJS/CCJc7tBr4sQ==", "optional": true }, - "@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "requires": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - } - }, - "@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==" - }, - "@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "requires": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - } - }, "@npmcli/agent": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-2.2.2.tgz", - "integrity": "sha512-OrcNPXdpSl9UX7qPVRWbmWMCSXrcDa2M9DvrbOTj7ao1S4PlqVFYv9/yLKMkrJKZ/V5A/kDBC690or307i26Og==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-3.0.0.tgz", + "integrity": "sha512-S79NdEgDQd/NGCay6TCoVzXSj74skRZIKJcpJjC5lOq34SZzyI6MqtiiWoiVWoVrTcGjNeC4ipbh1VIHlpfF5Q==", "dev": true, "requires": { "agent-base": "^7.1.0", @@ -5775,28 +6439,28 @@ } }, "@npmcli/fs": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-3.1.1.tgz", - "integrity": "sha512-q9CRWjpHCMIh5sVyefoD1cA7PkvILqCZsnSOEUUivORLjxCO/Irmue2DprETiNgEqktDBZaM1Bi+jrarx1XdCg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-4.0.0.tgz", + "integrity": "sha512-/xGlezI6xfGO9NwuJlnwz/K14qD1kCSAGtacBHnGzeAIuJGazcp45KP5NuyARXoKb7cwulAGWVsbeSxdG/cb0Q==", "dev": true, "requires": { "semver": "^7.3.5" } }, "@npmcli/git": { - "version": "5.0.7", - "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-5.0.7.tgz", - "integrity": "sha512-WaOVvto604d5IpdCRV2KjQu8PzkfE96d50CQGKgywXh2GxXmDeUO5EWcBC4V57uFyrNqx83+MewuJh3WTR3xPA==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-7.0.0.tgz", + "integrity": "sha512-vnz7BVGtOctJAIHouCJdvWBhsTVSICMeUgZo2c7XAi5d5Rrl80S1H7oPym7K03cRuinK5Q6s2dw36+PgXQTcMA==", "dev": true, "requires": { - "@npmcli/promise-spawn": "^7.0.0", - "lru-cache": "^10.0.1", - "npm-pick-manifest": "^9.0.0", - "proc-log": "^4.0.0", - "promise-inflight": "^1.0.1", + "@npmcli/promise-spawn": "^8.0.0", + "ini": "^5.0.0", + "lru-cache": "^11.2.1", + "npm-pick-manifest": "^11.0.1", + "proc-log": "^5.0.0", "promise-retry": "^2.0.1", "semver": "^7.3.5", - "which": "^4.0.0" + "which": "^5.0.0" }, "dependencies": { "isexe": { @@ -5805,10 +6469,16 @@ "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", "dev": true }, + "lru-cache": { + "version": "11.2.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.2.tgz", + "integrity": "sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==", + "dev": true + }, "which": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", - "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", + "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", "dev": true, "requires": { "isexe": "^3.1.1" @@ -5817,76 +6487,43 @@ } }, "@npmcli/installed-package-contents": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@npmcli/installed-package-contents/-/installed-package-contents-2.1.0.tgz", - "integrity": "sha512-c8UuGLeZpm69BryRykLuKRyKFZYJsZSCT4aVY5ds4omyZqJ172ApzgfKJ5eV/r3HgLdUYgFVe54KSFVjKoe27w==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/installed-package-contents/-/installed-package-contents-3.0.0.tgz", + "integrity": "sha512-fkxoPuFGvxyrH+OQzyTkX2LUEamrF4jZSmxjAtPPHHGO0dqsQ8tTKjnIS8SAnPHdk2I03BDtSMR5K/4loKg79Q==", "dev": true, "requires": { - "npm-bundled": "^3.0.0", - "npm-normalize-package-bin": "^3.0.0" + "npm-bundled": "^4.0.0", + "npm-normalize-package-bin": "^4.0.0" } }, "@npmcli/node-gyp": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@npmcli/node-gyp/-/node-gyp-3.0.0.tgz", - "integrity": "sha512-gp8pRXC2oOxu0DUE1/M3bYtb1b3/DbJ5aM113+XJBgfXdussRAsX0YOrOhdd8WvnAR6auDBvJomGAkLKA5ydxA==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/node-gyp/-/node-gyp-4.0.0.tgz", + "integrity": "sha512-+t5DZ6mO/QFh78PByMq1fGSAub/agLJZDRfJRMeOSNCt8s9YVlTjmGpIPwPhvXTGUIJk+WszlT0rQa1W33yzNA==", "dev": true }, "@npmcli/package-json": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/@npmcli/package-json/-/package-json-5.1.1.tgz", - "integrity": "sha512-uTq5j/UqUzbOaOxVy+osfOhpqOiLfUZ0Ut33UbcyyAPJbZcJsf4Mrsyb8r58FoIFlofw0iOFsuCA/oDK14VDJQ==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@npmcli/package-json/-/package-json-7.0.1.tgz", + "integrity": "sha512-956YUeI0YITbk2+KnirCkD19HLzES0habV+Els+dyZaVsaM6VGSiNwnRu6t3CZaqDLz4KXy2zx+0N/Zy6YjlAA==", "dev": true, "requires": { - "@npmcli/git": "^5.0.0", - "glob": "^10.2.2", - "hosted-git-info": "^7.0.0", - "json-parse-even-better-errors": "^3.0.0", - "normalize-package-data": "^6.0.0", - "proc-log": "^4.0.0", - "semver": "^7.5.3" - }, - "dependencies": { - "brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0" - } - }, - "glob": { - "version": "10.4.1", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.1.tgz", - "integrity": "sha512-2jelhlq3E4ho74ZyVLN03oKdAZVUa6UDZzFLVH1H7dnoax+y9qyaq8zBkfDIggjniU19z0wU18y16jMB2eyVIw==", - "dev": true, - "requires": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "path-scurry": "^1.11.1" - } - }, - "minimatch": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", - "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", - "dev": true, - "requires": { - "brace-expansion": "^2.0.1" - } - } + "@npmcli/git": "^7.0.0", + "glob": "^11.0.3", + "hosted-git-info": "^9.0.0", + "json-parse-even-better-errors": "^4.0.0", + "proc-log": "^5.0.0", + "semver": "^7.5.3", + "validate-npm-package-license": "^3.0.4" } }, "@npmcli/promise-spawn": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-7.0.2.tgz", - "integrity": "sha512-xhfYPXoV5Dy4UkY0D+v2KkwvnDfiA/8Mt3sWCGI/hM03NsYIH8ZaG6QzS9x7pje5vHZBZJ2v6VRFVTWACnqcmQ==", + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-8.0.3.tgz", + "integrity": "sha512-Yb00SWaL4F8w+K8YGhQ55+xE4RUNdMHV43WZGsiTM92gS+lC0mGsn7I4hLug7pbao035S6bj3Y3w0cUNGLfmkg==", "dev": true, "requires": { - "which": "^4.0.0" + "which": "^5.0.0" }, "dependencies": { "isexe": { @@ -5896,9 +6533,9 @@ "dev": true }, "which": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", - "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", + "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", "dev": true, "requires": { "isexe": "^3.1.1" @@ -5907,22 +6544,23 @@ } }, "@npmcli/redact": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@npmcli/redact/-/redact-1.1.0.tgz", - "integrity": "sha512-PfnWuOkQgu7gCbnSsAisaX7hKOdZ4wSAhAzH3/ph5dSGau52kCRrMMGbiSQLwyTZpgldkZ49b0brkOr1AzGBHQ==", + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@npmcli/redact/-/redact-3.2.2.tgz", + "integrity": "sha512-7VmYAmk4csGv08QzrDKScdzn11jHPFGyqJW39FyPgPuAp3zIaUmuCo1yxw9aGs+NEJuTGQ9Gwqpt93vtJubucg==", "dev": true }, "@npmcli/run-script": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-7.0.4.tgz", - "integrity": "sha512-9ApYM/3+rBt9V80aYg6tZfzj3UWdiYyCt7gJUD1VJKvWF5nwKDSICXbYIQbspFTq6TOpbsEtIC0LArB8d9PFmg==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-10.0.0.tgz", + "integrity": "sha512-vaQj4nccJbAslopIvd49pQH2NhUp7G9pY4byUtmwhe37ZZuubGrx0eB9hW2F37uVNRuDDK6byFGXF+7JCuMSZg==", "dev": true, "requires": { - "@npmcli/node-gyp": "^3.0.0", - "@npmcli/package-json": "^5.0.0", - "@npmcli/promise-spawn": "^7.0.0", - "node-gyp": "^10.0.0", - "which": "^4.0.0" + "@npmcli/node-gyp": "^4.0.0", + "@npmcli/package-json": "^7.0.0", + "@npmcli/promise-spawn": "^8.0.0", + "node-gyp": "^11.0.0", + "proc-log": "^5.0.0", + "which": "^5.0.0" }, "dependencies": { "isexe": { @@ -5932,9 +6570,9 @@ "dev": true }, "which": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", - "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", + "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", "dev": true, "requires": { "isexe": "^3.1.1" @@ -5949,239 +6587,319 @@ "optional": true }, "@sigstore/bundle": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/@sigstore/bundle/-/bundle-2.3.2.tgz", - "integrity": "sha512-wueKWDk70QixNLB363yHc2D2ItTgYiMTdPwK8D9dKQMR3ZQ0c35IxP5xnwQ8cNLoCgCRcHf14kE+CLIvNX1zmA==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@sigstore/bundle/-/bundle-4.0.0.tgz", + "integrity": "sha512-NwCl5Y0V6Di0NexvkTqdoVfmjTaQwoLM236r89KEojGmq/jMls8S+zb7yOwAPdXvbwfKDlP+lmXgAL4vKSQT+A==", "dev": true, "requires": { - "@sigstore/protobuf-specs": "^0.3.2" + "@sigstore/protobuf-specs": "^0.5.0" } }, "@sigstore/core": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@sigstore/core/-/core-1.1.0.tgz", - "integrity": "sha512-JzBqdVIyqm2FRQCulY6nbQzMpJJpSiJ8XXWMhtOX9eKgaXXpfNOF53lzQEjIydlStnd/eFtuC1dW4VYdD93oRg==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@sigstore/core/-/core-3.0.0.tgz", + "integrity": "sha512-NgbJ+aW9gQl/25+GIEGYcCyi8M+ng2/5X04BMuIgoDfgvp18vDcoNHOQjQsG9418HGNYRxG3vfEXaR1ayD37gg==", "dev": true }, "@sigstore/protobuf-specs": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@sigstore/protobuf-specs/-/protobuf-specs-0.3.2.tgz", - "integrity": "sha512-c6B0ehIWxMI8wiS/bj6rHMPqeFvngFV7cDU/MY+B16P9Z3Mp9k8L93eYZ7BYzSickzuqAQqAq0V956b3Ju6mLw==", + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@sigstore/protobuf-specs/-/protobuf-specs-0.5.0.tgz", + "integrity": "sha512-MM8XIwUjN2bwvCg1QvrMtbBmpcSHrkhFSCu1D11NyPvDQ25HEc4oG5/OcQfd/Tlf/OxmKWERDj0zGE23jQaMwA==", "dev": true }, "@sigstore/sign": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/@sigstore/sign/-/sign-2.3.2.tgz", - "integrity": "sha512-5Vz5dPVuunIIvC5vBb0APwo7qKA4G9yM48kPWJT+OEERs40md5GoUR1yedwpekWZ4m0Hhw44m6zU+ObsON+iDA==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@sigstore/sign/-/sign-4.0.1.tgz", + "integrity": "sha512-KFNGy01gx9Y3IBPG/CergxR9RZpN43N+lt3EozEfeoyqm8vEiLxwRl3ZO5sPx3Obv1ix/p7FWOlPc2Jgwfp9PA==", "dev": true, "requires": { - "@sigstore/bundle": "^2.3.2", - "@sigstore/core": "^1.0.0", - "@sigstore/protobuf-specs": "^0.3.2", - "make-fetch-happen": "^13.0.1", - "proc-log": "^4.2.0", + "@sigstore/bundle": "^4.0.0", + "@sigstore/core": "^3.0.0", + "@sigstore/protobuf-specs": "^0.5.0", + "make-fetch-happen": "^15.0.2", + "proc-log": "^5.0.0", "promise-retry": "^2.0.1" + }, + "dependencies": { + "@npmcli/agent": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-4.0.0.tgz", + "integrity": "sha512-kAQTcEN9E8ERLVg5AsGwLNoFb+oEG6engbqAU2P43gD4JEIkNGMHdVQ096FsOAAYpZPB0RSt0zgInKIAS1l5QA==", + "dev": true, + "requires": { + "agent-base": "^7.1.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.1", + "lru-cache": "^11.2.1", + "socks-proxy-agent": "^8.0.3" + } + }, + "lru-cache": { + "version": "11.2.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.2.tgz", + "integrity": "sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==", + "dev": true + }, + "make-fetch-happen": { + "version": "15.0.2", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-15.0.2.tgz", + "integrity": "sha512-sI1NY4lWlXBAfjmCtVWIIpBypbBdhHtcjnwnv+gtCnsaOffyFil3aidszGC8hgzJe+fT1qix05sWxmD/Bmf/oQ==", + "dev": true, + "requires": { + "@npmcli/agent": "^4.0.0", + "cacache": "^20.0.1", + "http-cache-semantics": "^4.1.1", + "minipass": "^7.0.2", + "minipass-fetch": "^4.0.0", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^1.0.0", + "proc-log": "^5.0.0", + "promise-retry": "^2.0.1", + "ssri": "^12.0.0" + } + } } }, "@sigstore/tuf": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/@sigstore/tuf/-/tuf-2.3.4.tgz", - "integrity": "sha512-44vtsveTPUpqhm9NCrbU8CWLe3Vck2HO1PNLw7RIajbB7xhtn5RBPm1VNSCMwqGYHhDsBJG8gDF0q4lgydsJvw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@sigstore/tuf/-/tuf-4.0.0.tgz", + "integrity": "sha512-0QFuWDHOQmz7t66gfpfNO6aEjoFrdhkJaej/AOqb4kqWZVbPWFZifXZzkxyQBB1OwTbkhdT3LNpMFxwkTvf+2w==", "dev": true, "requires": { - "@sigstore/protobuf-specs": "^0.3.2", - "tuf-js": "^2.2.1" + "@sigstore/protobuf-specs": "^0.5.0", + "tuf-js": "^4.0.0" } }, "@sigstore/verify": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@sigstore/verify/-/verify-1.2.1.tgz", - "integrity": "sha512-8iKx79/F73DKbGfRf7+t4dqrc0bRr0thdPrxAtCKWRm/F0tG71i6O1rvlnScncJLLBZHn3h8M3c1BSUAb9yu8g==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@sigstore/verify/-/verify-3.0.0.tgz", + "integrity": "sha512-moXtHH33AobOhTZF8xcX1MpOFqdvfCk7v6+teJL8zymBiDXwEsQH6XG9HGx2VIxnJZNm4cNSzflTLDnQLmIdmw==", "dev": true, "requires": { - "@sigstore/bundle": "^2.3.2", - "@sigstore/core": "^1.1.0", - "@sigstore/protobuf-specs": "^0.3.2" + "@sigstore/bundle": "^4.0.0", + "@sigstore/core": "^3.0.0", + "@sigstore/protobuf-specs": "^0.5.0" } }, "@tapjs/after": { - "version": "1.1.24", - "resolved": "https://registry.npmjs.org/@tapjs/after/-/after-1.1.24.tgz", - "integrity": "sha512-Qys3CtftkfHGC7thDGm9TBzRCBLAoJKrXufF1zQxI1oNUjclWZP/s8CtHH0mwUTISOTehmBLV3wPPHSslD67Ng==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@tapjs/after/-/after-3.0.2.tgz", + "integrity": "sha512-Xb0bqJWXfp6VVSx1T96lNJM67v2XxJCG3o7mH77weB+RzwAuf0uzGYy/hxP+nUAWh9yH+lHzuHclL+DR8Zlu3Q==", "dev": true, "requires": { "is-actual-promise": "^1.0.1" } }, "@tapjs/after-each": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@tapjs/after-each/-/after-each-2.0.1.tgz", - "integrity": "sha512-3JXIJ4g9LPjyXmn/1VuIMC0vh7uBgUpQPksjffxv0rL8wq4C8lvmqt8Qu/fVImJucqzA+WrRqVG1b2Ab0ocDOw==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@tapjs/after-each/-/after-each-4.0.2.tgz", + "integrity": "sha512-OQEENy55qtJ7WaMnIQvf0YaV4/YjI2B9+ezn679Vrptg/xMbaG5fSJz/Z5BFroh05HiWRo+MGu66q7Lb00kDJg==", "dev": true, "requires": { "function-loop": "^4.0.0" } }, "@tapjs/asserts": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@tapjs/asserts/-/asserts-2.0.1.tgz", - "integrity": "sha512-v2xYDLUwMGt8pzoY5LIjDCaw2NM+G01NW4pC3RcpsZLZbzQv1x/phi2RAX0ixI0nCmZZybqRygFKuMcJamS+gg==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@tapjs/asserts/-/asserts-4.0.2.tgz", + "integrity": "sha512-XeAYvYMu61/Gc9Dpn+0QZCjUTbbQE7DckiZNhQNMuMwAXUDEZR/TbxJNysIdMWq2ag75TQQ6ylCUJovy7HPDlg==", "dev": true, "requires": { - "@tapjs/stack": "2.0.1", + "@tapjs/stack": "4.0.1", "is-actual-promise": "^1.0.1", - "tcompare": "7.0.1", + "tcompare": "9.0.1", "trivial-deferred": "^2.0.0" }, "dependencies": { + "diff": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.2.tgz", + "integrity": "sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg==", + "dev": true + }, "tcompare": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/tcompare/-/tcompare-7.0.1.tgz", - "integrity": "sha512-JN5s7hgmg/Ya5HxZqCnywT+XiOGRFcJRgYhtMyt/1m+h0yWpWwApO7HIM8Bpwyno9hI151ljjp5eAPCHhIGbpQ==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/tcompare/-/tcompare-9.0.1.tgz", + "integrity": "sha512-P9i7K3RnKWb3mH0AmDCChVd7FP0LEo4VSBJMhjKB+Rg+Tvq09ytnys8ygsx5ve1di2fI4IW+HhjhIXoeiRVogw==", "dev": true, "requires": { - "diff": "^5.2.0", + "diff": "^8.0.2", "react-element-to-jsx-string": "^15.0.0" } } } }, "@tapjs/before": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@tapjs/before/-/before-2.0.1.tgz", - "integrity": "sha512-GgnlWPm2PbuyYuG4gkkO2KAvT/BbGnpKs60U4XzPSJ2w73Qc/IYWP0Kz6qfCWongpiLteoco67M89ujUQApYJw==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@tapjs/before/-/before-4.0.2.tgz", + "integrity": "sha512-ODi0rXOqCWZLS1j6fJ2iyqaXy6B/y75x8Y940hRAe0DfPPf48IgZY84+GfWWVIkRqmbYZS/F0rwcegPHxI/xHg==", "dev": true, "requires": { "is-actual-promise": "^1.0.1" } }, "@tapjs/before-each": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@tapjs/before-each/-/before-each-2.0.1.tgz", - "integrity": "sha512-gG1nYkvCHtWwhkueulO475KczdQZ3vBRgdkta/Qi42ZjZo6SNhYVjNc/+LRGV5vZoESrvgSd+JrDRGufd+j43w==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@tapjs/before-each/-/before-each-4.0.2.tgz", + "integrity": "sha512-CXNWJ/pvqu3DwNjgnOX9zfmxYz7OvxQ4w7X/1uyFUblWarYYOUqtLsF/FrBHn3rqOowNtP5KJAzY9Wx5UT3r+g==", "dev": true, "requires": { "function-loop": "^4.0.0" } }, + "@tapjs/chdir": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@tapjs/chdir/-/chdir-3.0.2.tgz", + "integrity": "sha512-KYDxkkzGeEhAT33VDWqIx8FRLk+cOzVsj/E0JBRd45n18EHT7C/wE/Nqhu+843nVhABp/c5BZMb+Rib/fCQV1g==", + "dev": true, + "requires": {} + }, "@tapjs/config": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@tapjs/config/-/config-3.0.1.tgz", - "integrity": "sha512-gAYFzErdSuPQ3afW6iRR99hiJmRLU+x9T+NE89z9UM45iPxglWLrRv1PFfh3tmtX6rpzwD5RY4/FVPcP2+/1LQ==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@tapjs/config/-/config-5.0.2.tgz", + "integrity": "sha512-VfvYwcRGC3fbBAuRZs8VheA4YTa1CaJlYonHM2YM1Uu+UGeLCmvxJjIOBauhtp+F/QczUsJxqOjDy9z9ny1UYQ==", "dev": true, "requires": { - "@tapjs/core": "2.0.1", - "@tapjs/test": "2.0.1", - "chalk": "^5.2.0", - "jackspeak": "^3.1.2", - "polite-json": "^4.0.1", - "tap-yaml": "2.2.2", - "walk-up-path": "^3.0.1" + "@tapjs/core": "4.0.2", + "@tapjs/test": "4.0.2", + "chalk": "^5.6.2", + "jackspeak": "^4.0.1", + "polite-json": "^5.0.0", + "tap-yaml": "4.0.1", + "walk-up-path": "^4.0.0" }, "dependencies": { "chalk": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", - "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", "dev": true + }, + "jackspeak": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.1.1.tgz", + "integrity": "sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==", + "dev": true, + "requires": { + "@isaacs/cliui": "^8.0.2" + } } } }, "@tapjs/core": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@tapjs/core/-/core-2.0.1.tgz", - "integrity": "sha512-q+8d+ohw5kudktIqgP5ETBcPWAPip+kMIxs2eL2G3dV+7Gc8WrH43cCPrbSGPRITIOSIDPrtpQZEcZwQNqDdQw==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@tapjs/core/-/core-4.0.2.tgz", + "integrity": "sha512-kzM90qsqHAJOTUMVjB3G26c3Ka4/HFg253lSt3pxBFesIHneIZe6Fre1NEWnHZPLmmzBO6HRRffc9zDxXcaeRw==", "dev": true, "requires": { - "@tapjs/processinfo": "^3.1.7", - "@tapjs/stack": "2.0.1", - "@tapjs/test": "2.0.1", + "@tapjs/processinfo": "^3.1.8", + "@tapjs/stack": "4.0.1", + "@tapjs/test": "4.0.2", "async-hook-domain": "^4.0.1", - "diff": "^5.2.0", + "diff": "^8.0.2", "is-actual-promise": "^1.0.1", "minipass": "^7.0.4", "signal-exit": "4.1", - "tap-parser": "16.0.1", - "tap-yaml": "2.2.2", - "tcompare": "7.0.1", + "tap-parser": "18.0.1", + "tap-yaml": "4.0.1", + "tcompare": "9.0.1", "trivial-deferred": "^2.0.0" }, "dependencies": { + "diff": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.2.tgz", + "integrity": "sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg==", + "dev": true + }, "tcompare": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/tcompare/-/tcompare-7.0.1.tgz", - "integrity": "sha512-JN5s7hgmg/Ya5HxZqCnywT+XiOGRFcJRgYhtMyt/1m+h0yWpWwApO7HIM8Bpwyno9hI151ljjp5eAPCHhIGbpQ==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/tcompare/-/tcompare-9.0.1.tgz", + "integrity": "sha512-P9i7K3RnKWb3mH0AmDCChVd7FP0LEo4VSBJMhjKB+Rg+Tvq09ytnys8ygsx5ve1di2fI4IW+HhjhIXoeiRVogw==", "dev": true, "requires": { - "diff": "^5.2.0", + "diff": "^8.0.2", "react-element-to-jsx-string": "^15.0.0" } } } }, "@tapjs/error-serdes": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@tapjs/error-serdes/-/error-serdes-2.0.1.tgz", - "integrity": "sha512-P+M4rtcfkDsUveKKmoRNF+07xpbPnRY5KrstIUOnyn483clQ7BJhsnWr162yYNCsyOj4zEfZmAJI1f8Bi7h/ZA==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@tapjs/error-serdes/-/error-serdes-4.0.1.tgz", + "integrity": "sha512-8GiOXbgGIRBcSGnPXYuiboy0xJQDMP2OcILnghHX/jzJKi2l9mxX6FTonOWj/0qsf5Ji5Z4/DIKRcYINIxaejg==", "dev": true, "requires": { "minipass": "^7.0.4" } }, "@tapjs/filter": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@tapjs/filter/-/filter-2.0.1.tgz", - "integrity": "sha512-muKEeXK7Tz6VR4hjXfT2qXPvjYES575mtiRerjHf+8qP8D7MvmC8qDZJjzFdo1nZHKhF8snvFosIVuI1BAhvsw==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@tapjs/filter/-/filter-4.0.2.tgz", + "integrity": "sha512-Pfi1u1naoUL4G4AoJOUEUQauaZeF4gGk8MZ8It2ht60gopYsxy9sSmIKlKPvZm13f110pZxJfO+hogappAmDfw==", "dev": true, "requires": {} }, "@tapjs/fixture": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@tapjs/fixture/-/fixture-2.0.1.tgz", - "integrity": "sha512-MLgEwsBlCD69iUbZcnKBehP2js5cV4p5GrFoOKSudMuH2DQJInaF/g2bkijue61cVZwPj/MRPCqAlkwA94epjg==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@tapjs/fixture/-/fixture-4.0.2.tgz", + "integrity": "sha512-h161PA9ngT3zG6iLybAUlrSWx31OFx7Av1aR4FHvui9IkseXaF0x6gxS0MMaXwdyRYrD7lFvmU7SRTDlvvdeBA==", "dev": true, "requires": { "mkdirp": "^3.0.0", - "rimraf": "^5.0.5" + "rimraf": "^6.0.0" + }, + "dependencies": { + "rimraf": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-6.0.1.tgz", + "integrity": "sha512-9dkvaxAsk/xNXSJzMgFqqMCuFgt2+KsOFek3TMLfo8NCPfWpBmqwyNn5Y+NX56QUYfCtsyhF3ayiboEoUmJk/A==", + "dev": true, + "requires": { + "glob": "^11.0.0", + "package-json-from-dist": "^1.0.0" + } + } } }, "@tapjs/intercept": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@tapjs/intercept/-/intercept-2.0.1.tgz", - "integrity": "sha512-BZgXE3zCAbv4lfbph1r85gihtI3kXltHlFQ8Bf3Yy9fx27DKQlBvXnD7T69ke8kQLRzhz+wTMcR/mcQjo1fa7w==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@tapjs/intercept/-/intercept-4.0.2.tgz", + "integrity": "sha512-rlZcO/Yf9vU8ypfQEO0c/QUCcGCr0IMJltg53805ILuNhTDR7x0vlJSvJ7fLsuoezkUwXlWdlED6ZqIID7RokA==", "dev": true, "requires": { - "@tapjs/after": "1.1.24", - "@tapjs/stack": "2.0.1" + "@tapjs/after": "3.0.2", + "@tapjs/stack": "4.0.1" } }, "@tapjs/mock": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@tapjs/mock/-/mock-2.0.1.tgz", - "integrity": "sha512-i1vkwNgO7uEuQW3+hTuE2L64aC9xk0cC3PtC6DZKqyApk2IstNgoIS38nfsI6v2kvEgZNuWlsNcRAYNDOIEhzA==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@tapjs/mock/-/mock-4.0.2.tgz", + "integrity": "sha512-48o3xz3Xl83Ei25KOR5aWytbCYUl33GQAoQm4khFpTyI/v3fjyR9jDgFLAT7IWix8tRrrkMf+PynmZkWVQ4oKA==", "dev": true, "requires": { - "@tapjs/after": "1.1.24", - "@tapjs/stack": "2.0.1", - "resolve-import": "^1.4.5", - "walk-up-path": "^3.0.1" + "@tapjs/after": "3.0.2", + "@tapjs/stack": "4.0.1", + "resolve-import": "^2.0.0", + "walk-up-path": "^4.0.0" } }, "@tapjs/node-serialize": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@tapjs/node-serialize/-/node-serialize-2.0.1.tgz", - "integrity": "sha512-1GtHDa7AXpk8y08llIPfUKRTDNsq+BhXxz7wiIfVEAOEB09kGyfpWteOg+cmvb+aHU1Ays3z+medXTIBm0D5Kg==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@tapjs/node-serialize/-/node-serialize-4.0.2.tgz", + "integrity": "sha512-oparVnZqbwF7O7IQn4nfpnsddtmlkWPB033uzZbvSW6v0U6T8DBENgKAfzJ2sNQH3f2Xaa3dqK+N5brwQa3P2g==", "dev": true, "requires": { - "@tapjs/error-serdes": "2.0.1", - "@tapjs/stack": "2.0.1", - "tap-parser": "16.0.1" + "@tapjs/error-serdes": "4.0.1", + "@tapjs/stack": "4.0.1", + "tap-parser": "18.0.1" } }, "@tapjs/processinfo": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/@tapjs/processinfo/-/processinfo-3.1.7.tgz", - "integrity": "sha512-SI5RJQ5HnUKEWnHSAF6hOm6XPdnjZ+CJzIaVHdFebed8iDAPTqb+IwMVu9yq9+VQ7FRsMMlgLL2SW4rss2iJbQ==", + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/@tapjs/processinfo/-/processinfo-3.1.8.tgz", + "integrity": "sha512-FIriEB+qqArPhmVYc1PZwRHD99myRdl7C9Oe/uts04Q2LOxQ5MEmqP9XOP8vVYzpDOYwmL8OmL6eOYt9eZlQKQ==", "dev": true, "requires": { "pirates": "^4.0.5", @@ -6191,141 +6909,158 @@ } }, "@tapjs/reporter": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@tapjs/reporter/-/reporter-2.0.1.tgz", - "integrity": "sha512-fCdl4vg8vnlqIYtTQ9dc3zOqeXrA5QbATbT4dsPIiPuCM3gvKTbntaNBeaWWZkPx697Dj+b8TIxT/xhNMNv7jQ==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@tapjs/reporter/-/reporter-4.0.3.tgz", + "integrity": "sha512-Mdx3+C3f4q3llawja5V2RVIfFzr1KhEm57i9xUJTgM5kHcPjrMw14aQUCwYxLSYSd5esOubnnV5lD9rxQb+tyQ==", "dev": true, "requires": { - "@tapjs/config": "3.0.1", - "@tapjs/stack": "2.0.1", - "chalk": "^5.2.0", - "ink": "^4.4.1", + "@tapjs/config": "5.0.2", + "@tapjs/stack": "4.0.1", + "chalk": "^5.6.2", + "ink": "^5.2.1", "minipass": "^7.0.4", "ms": "^2.1.3", "patch-console": "^2.0.0", "prismjs-terminal": "^1.2.3", "react": "^18.2.0", "string-length": "^6.0.0", - "tap-parser": "16.0.1", - "tap-yaml": "2.2.2", - "tcompare": "7.0.1" + "tap-parser": "18.0.1", + "tap-yaml": "4.0.1", + "tcompare": "9.0.1" }, "dependencies": { "chalk": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", - "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", "dev": true }, - "ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "diff": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.2.tgz", + "integrity": "sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg==", "dev": true }, "tcompare": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/tcompare/-/tcompare-7.0.1.tgz", - "integrity": "sha512-JN5s7hgmg/Ya5HxZqCnywT+XiOGRFcJRgYhtMyt/1m+h0yWpWwApO7HIM8Bpwyno9hI151ljjp5eAPCHhIGbpQ==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/tcompare/-/tcompare-9.0.1.tgz", + "integrity": "sha512-P9i7K3RnKWb3mH0AmDCChVd7FP0LEo4VSBJMhjKB+Rg+Tvq09ytnys8ygsx5ve1di2fI4IW+HhjhIXoeiRVogw==", "dev": true, "requires": { - "diff": "^5.2.0", + "diff": "^8.0.2", "react-element-to-jsx-string": "^15.0.0" } } } }, "@tapjs/run": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@tapjs/run/-/run-2.0.2.tgz", - "integrity": "sha512-2hPGlabqbLb3hh4BHHvwE8R9a9OiWumkCkHw5QQUZurDsVOpB94FfteqW9mktTVjZJnN0go+sN3GN2jZUaPWGQ==", - "dev": true, - "requires": { - "@tapjs/after": "1.1.24", - "@tapjs/before": "2.0.1", - "@tapjs/config": "3.0.1", - "@tapjs/processinfo": "^3.1.7", - "@tapjs/reporter": "2.0.1", - "@tapjs/spawn": "2.0.1", - "@tapjs/stdin": "2.0.1", - "@tapjs/test": "2.0.1", - "c8": "^9.1.0", - "chalk": "^5.3.0", - "chokidar": "^3.6.0", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@tapjs/run/-/run-4.0.3.tgz", + "integrity": "sha512-kkoMk5OJPgxigZZwu47ix2Zo8U64t8YiFiz+Iec5HrSW1BNgQyhurfMbc8uGjBHh3OhvJBaWhWuzctn0ROJrEQ==", + "dev": true, + "requires": { + "@tapjs/after": "3.0.2", + "@tapjs/before": "4.0.2", + "@tapjs/config": "5.0.2", + "@tapjs/processinfo": "^3.1.8", + "@tapjs/reporter": "4.0.3", + "@tapjs/spawn": "4.0.2", + "@tapjs/stdin": "4.0.2", + "@tapjs/test": "4.0.2", + "c8": "^10.1.3", + "chalk": "^5.6.2", + "chokidar": "^4.0.2", "foreground-child": "^3.1.1", - "glob": "^10.3.16", + "glob": "^11.0.0", "minipass": "^7.0.4", "mkdirp": "^3.0.1", "opener": "^1.5.2", - "pacote": "^17.0.6", - "resolve-import": "^1.4.5", - "rimraf": "^5.0.5", - "semver": "^7.6.0", + "pacote": "^21.0.3", + "path-scurry": "^2.0.0", + "resolve-import": "^2.0.0", + "rimraf": "^6.0.0", + "semver": "^7.7.2", "signal-exit": "^4.1.0", - "tap-parser": "16.0.1", - "tap-yaml": "2.2.2", - "tcompare": "7.0.1", + "tap-parser": "18.0.1", + "tap-yaml": "4.0.1", + "tcompare": "9.0.1", "trivial-deferred": "^2.0.0", - "which": "^4.0.0" + "which": "^5.0.0" }, "dependencies": { - "brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0" - } - }, "chalk": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", - "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", "dev": true }, - "glob": { - "version": "10.4.1", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.1.tgz", - "integrity": "sha512-2jelhlq3E4ho74ZyVLN03oKdAZVUa6UDZzFLVH1H7dnoax+y9qyaq8zBkfDIggjniU19z0wU18y16jMB2eyVIw==", + "chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", "dev": true, "requires": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "path-scurry": "^1.11.1" + "readdirp": "^4.0.1" } }, + "diff": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.2.tgz", + "integrity": "sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg==", + "dev": true + }, "isexe": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", "dev": true }, - "minimatch": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", - "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", + "lru-cache": { + "version": "11.2.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.2.tgz", + "integrity": "sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==", + "dev": true + }, + "path-scurry": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", + "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==", "dev": true, "requires": { - "brace-expansion": "^2.0.1" + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + } + }, + "readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true + }, + "rimraf": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-6.0.1.tgz", + "integrity": "sha512-9dkvaxAsk/xNXSJzMgFqqMCuFgt2+KsOFek3TMLfo8NCPfWpBmqwyNn5Y+NX56QUYfCtsyhF3ayiboEoUmJk/A==", + "dev": true, + "requires": { + "glob": "^11.0.0", + "package-json-from-dist": "^1.0.0" } }, "tcompare": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/tcompare/-/tcompare-7.0.1.tgz", - "integrity": "sha512-JN5s7hgmg/Ya5HxZqCnywT+XiOGRFcJRgYhtMyt/1m+h0yWpWwApO7HIM8Bpwyno9hI151ljjp5eAPCHhIGbpQ==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/tcompare/-/tcompare-9.0.1.tgz", + "integrity": "sha512-P9i7K3RnKWb3mH0AmDCChVd7FP0LEo4VSBJMhjKB+Rg+Tvq09ytnys8ygsx5ve1di2fI4IW+HhjhIXoeiRVogw==", "dev": true, "requires": { - "diff": "^5.2.0", + "diff": "^8.0.2", "react-element-to-jsx-string": "^15.0.0" } }, "which": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", - "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", + "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", "dev": true, "requires": { "isexe": "^3.1.1" @@ -6334,141 +7069,137 @@ } }, "@tapjs/snapshot": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@tapjs/snapshot/-/snapshot-2.0.1.tgz", - "integrity": "sha512-ZnbCxL+9fiJ38tec6wvRtRBZz9ChRUq0Bov7dltdZMNkXqudKyB+Zzbg25bqDEIgcczyp6A9hOwTX6VybDGqpg==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@tapjs/snapshot/-/snapshot-4.0.2.tgz", + "integrity": "sha512-PWlWNEH+4x0oN8nemk+2rk3jub2L/7c6A383SD15GadGOT4hYvckqY2mCZarMAoV5xErFZOglGTD9do83TWMPQ==", "dev": true, "requires": { "is-actual-promise": "^1.0.1", - "tcompare": "7.0.1", + "tcompare": "9.0.1", "trivial-deferred": "^2.0.0" }, "dependencies": { + "diff": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.2.tgz", + "integrity": "sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg==", + "dev": true + }, "tcompare": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/tcompare/-/tcompare-7.0.1.tgz", - "integrity": "sha512-JN5s7hgmg/Ya5HxZqCnywT+XiOGRFcJRgYhtMyt/1m+h0yWpWwApO7HIM8Bpwyno9hI151ljjp5eAPCHhIGbpQ==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/tcompare/-/tcompare-9.0.1.tgz", + "integrity": "sha512-P9i7K3RnKWb3mH0AmDCChVd7FP0LEo4VSBJMhjKB+Rg+Tvq09ytnys8ygsx5ve1di2fI4IW+HhjhIXoeiRVogw==", "dev": true, "requires": { - "diff": "^5.2.0", + "diff": "^8.0.2", "react-element-to-jsx-string": "^15.0.0" } } } }, "@tapjs/spawn": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@tapjs/spawn/-/spawn-2.0.1.tgz", - "integrity": "sha512-3VaQKJjHV5frMZj3Ef+QlJyB6b7VsGMil223zAEz8Ttgy2hDYtcb29nvsLPUcowFyOUrsydnXEnHgpR79wEPOA==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@tapjs/spawn/-/spawn-4.0.2.tgz", + "integrity": "sha512-CNOrZFXh+mm2iul/cGRgVA4HMv9GxmtbgQSICMjRrj8VLDzMboW862P+wEvfYrHeXdCGAGWKqTIcsX5pXQVQRA==", "dev": true, "requires": {} }, "@tapjs/stack": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@tapjs/stack/-/stack-2.0.1.tgz", - "integrity": "sha512-3rKbZkRkLeJl9ilV/6b80YfI4C4+OYf7iEz5/d0MIVhmVvxv0ttIy5JnZutAc4Gy9eRp5Ne5UTAIFOVY5k36cg==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@tapjs/stack/-/stack-4.0.1.tgz", + "integrity": "sha512-Rbyz4XMuZWNxCs+/j0c5idFz4MKBo7uSaNvk6R7Al9jQJzk7Lv0WC2lWW0CV+7t/TUynTFxEwAaY5pIM752WQg==", "dev": true }, "@tapjs/stdin": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@tapjs/stdin/-/stdin-2.0.1.tgz", - "integrity": "sha512-5Oe13Fzpnt9seAi8h3bsMxtJp8S+DQI6ncBD9JBcS91XKLbqyKrb1bNzeXQN2PrHBs6Atw8cOzFZh0TjL+bIaA==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@tapjs/stdin/-/stdin-4.0.2.tgz", + "integrity": "sha512-6e/jQ0I9G6DC9m6Yj3jC6sNAmiwvrPVni3iMpJn3GICs0dROyx1m9wnMBK5wgKNgN2AXfvEOtf5Cby124eHeJQ==", "dev": true, "requires": {} }, "@tapjs/test": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@tapjs/test/-/test-2.0.1.tgz", - "integrity": "sha512-PKazf7r4+bLFATML2f/h8glGcSirXmzXUYlhFuxb4xHoOhHojyKgo1p8kSj+Ksxb3hVSCQlvyXgM8QYYaoMwog==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@tapjs/test/-/test-4.0.2.tgz", + "integrity": "sha512-J8WOSesfqp6/P5UbChDI5xzREQ96787ZFHLliPva4oi5XevG1TWtfSL47HtbQUtKvWSC7YIWW3CQhcBHRGk6Vg==", "dev": true, "requires": { "@isaacs/ts-node-temp-fork-for-pr-2009": "^10.9.7", - "@tapjs/after": "1.1.24", - "@tapjs/after-each": "2.0.1", - "@tapjs/asserts": "2.0.1", - "@tapjs/before": "2.0.1", - "@tapjs/before-each": "2.0.1", - "@tapjs/filter": "2.0.1", - "@tapjs/fixture": "2.0.1", - "@tapjs/intercept": "2.0.1", - "@tapjs/mock": "2.0.1", - "@tapjs/node-serialize": "2.0.1", - "@tapjs/snapshot": "2.0.1", - "@tapjs/spawn": "2.0.1", - "@tapjs/stdin": "2.0.1", - "@tapjs/typescript": "1.4.6", - "@tapjs/worker": "2.0.1", - "glob": "^10.3.16", - "jackspeak": "^3.1.2", + "@tapjs/after": "3.0.2", + "@tapjs/after-each": "4.0.2", + "@tapjs/asserts": "4.0.2", + "@tapjs/before": "4.0.2", + "@tapjs/before-each": "4.0.2", + "@tapjs/chdir": "3.0.2", + "@tapjs/filter": "4.0.2", + "@tapjs/fixture": "4.0.2", + "@tapjs/intercept": "4.0.2", + "@tapjs/mock": "4.0.2", + "@tapjs/node-serialize": "4.0.2", + "@tapjs/snapshot": "4.0.2", + "@tapjs/spawn": "4.0.2", + "@tapjs/stdin": "4.0.2", + "@tapjs/typescript": "3.1.1", + "@tapjs/worker": "4.0.2", + "glob": "^11.0.3", + "jackspeak": "^4.0.1", "mkdirp": "^3.0.0", - "resolve-import": "^1.4.5", - "rimraf": "^5.0.5", - "sync-content": "^1.0.1", - "tap-parser": "16.0.1", - "tshy": "^1.14.0", - "typescript": "5.4", - "walk-up-path": "^3.0.1" - }, - "dependencies": { - "brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "package-json-from-dist": "^1.0.0", + "resolve-import": "^2.0.0", + "rimraf": "^6.0.0", + "sync-content": "^2.0.1", + "tap-parser": "18.0.1", + "tshy": "^3.0.3", + "typescript": "5.9", + "walk-up-path": "^4.0.0" + }, + "dependencies": { + "jackspeak": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.1.1.tgz", + "integrity": "sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==", "dev": true, "requires": { - "balanced-match": "^1.0.0" + "@isaacs/cliui": "^8.0.2" } }, - "glob": { - "version": "10.4.1", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.1.tgz", - "integrity": "sha512-2jelhlq3E4ho74ZyVLN03oKdAZVUa6UDZzFLVH1H7dnoax+y9qyaq8zBkfDIggjniU19z0wU18y16jMB2eyVIw==", - "dev": true, - "requires": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "path-scurry": "^1.11.1" - } - }, - "minimatch": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", - "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", + "rimraf": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-6.0.1.tgz", + "integrity": "sha512-9dkvaxAsk/xNXSJzMgFqqMCuFgt2+KsOFek3TMLfo8NCPfWpBmqwyNn5Y+NX56QUYfCtsyhF3ayiboEoUmJk/A==", "dev": true, "requires": { - "brace-expansion": "^2.0.1" + "glob": "^11.0.0", + "package-json-from-dist": "^1.0.0" } } } }, "@tapjs/typescript": { - "version": "1.4.6", - "resolved": "https://registry.npmjs.org/@tapjs/typescript/-/typescript-1.4.6.tgz", - "integrity": "sha512-6jxUQ7Mdb+Y2q8RJcwgZZ6dCR+X2u3hCL+xb1GDAtO7k1+B6z2b+z+I+FdhuO4YgrP0SLRjocL5rJM/xi9K7qw==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@tapjs/typescript/-/typescript-3.1.1.tgz", + "integrity": "sha512-2fxDTeL8X3sm9g0KnHZSD/p9o8tFWYhswRKUq//jv9FA/4XetsKs+ApddPUJEi3AX8+Ma1P1EzjSy/f0z5KIJA==", "dev": true, "requires": { "@isaacs/ts-node-temp-fork-for-pr-2009": "^10.9.7" } }, "@tapjs/worker": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@tapjs/worker/-/worker-2.0.1.tgz", - "integrity": "sha512-wegz8IxNEPIIAA+R76/avZgNmZ4iC7QGFbtXKGBU962/1lXTITxshRV6e21r0IBa7YLkSVgDuVSVB3+Qzve0Yg==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@tapjs/worker/-/worker-4.0.2.tgz", + "integrity": "sha512-o2jzyjEnpRf3xHmduIfO/HXb5m9qRz+tCoiiFmPav+LC7eilSrDP3eGiQwMGbew64PK52KQwQW/LYNRrQsuMxA==", "dev": true, "requires": {} }, "@tsconfig/node14": { - "version": "14.1.2", - "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-14.1.2.tgz", - "integrity": "sha512-1vncsbfCZ3TBLPxesRYz02Rn7SNJfbLoDVkcZ7F/ixOV6nwxwgdhD1mdPcc5YQ413qBJ8CvMxXMFfJ7oawjo7Q==", + "version": "14.1.5", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-14.1.5.tgz", + "integrity": "sha512-hsldDMdbjF18BgvqFX6rHwqb0wlDh4lxyXmo3VATa7LwL4AFrHijv8Or9ySXBSg9TyysRkldJyAC/kplyF/Mmg==", "dev": true }, "@tsconfig/node16": { - "version": "16.1.3", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-16.1.3.tgz", - "integrity": "sha512-9nTOUBn+EMKO6rtSZJk+DcqsfgtlERGT9XPJ5PRj/HNENPCBY1yu/JEj5wT6GLtbCLBO2k46SeXDaY0pjMqypw==", + "version": "16.1.5", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-16.1.5.tgz", + "integrity": "sha512-P4GBZdpi6i5NGB12pwLH4oAIPrxl1ZvD8gxA4E7VdUUCik34J7FerrxzoDXtT4ugbrRjLbK5WuHPOUNZJOxuaQ==", "dev": true }, "@tsconfig/node18": { @@ -6478,9 +7209,9 @@ "dev": true }, "@tsconfig/node20": { - "version": "20.1.4", - "resolved": "https://registry.npmjs.org/@tsconfig/node20/-/node20-20.1.4.tgz", - "integrity": "sha512-sqgsT69YFeLWf5NtJ4Xq/xAF8p4ZQHlmGW74Nu2tD4+g5fAsposc4ZfaaPixVu4y01BEiDCWLRDCvDM5JOsRxg==", + "version": "20.1.6", + "resolved": "https://registry.npmjs.org/@tsconfig/node20/-/node20-20.1.6.tgz", + "integrity": "sha512-sz+Hqx9zwZDpZIV871WSbUzSqNIsXzghZydypnfgzPKLltVJfkINfUeTct31n/tTSa9ZE1ZOfKdRre1uHHquYQ==", "dev": true }, "@tufjs/canonical-json": { @@ -6490,28 +7221,28 @@ "dev": true }, "@tufjs/models": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@tufjs/models/-/models-2.0.1.tgz", - "integrity": "sha512-92F7/SFyufn4DXsha9+QfKnN03JGqtMFMXgSHbZOo8JG59WkTni7UzAouNQDf7AuP9OAMxVOPQcqG3sB7w+kkg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@tufjs/models/-/models-4.0.0.tgz", + "integrity": "sha512-h5x5ga/hh82COe+GoD4+gKUeV4T3iaYOxqLt41GRKApinPI7DMidhCmNVTjKfhCWFJIGXaFJee07XczdT4jdZQ==", "dev": true, "requires": { "@tufjs/canonical-json": "2.0.0", - "minimatch": "^9.0.4" + "minimatch": "^9.0.5" }, "dependencies": { "brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "requires": { "balanced-match": "^1.0.0" } }, "minimatch": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", - "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "dev": true, "requires": { "brace-expansion": "^2.0.1" @@ -6519,32 +7250,42 @@ } } }, + "@types/estree": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", + "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==" + }, "@types/istanbul-lib-coverage": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", "dev": true }, + "@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==" + }, "@types/node": { - "version": "20.13.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.13.0.tgz", - "integrity": "sha512-FM6AOb3khNkNIXPnHFDYaHerSv8uN22C91z098AnGccVu+Pcdhi+pNUFDi0iLmPIsVE0JBD0KVS7mzUYt4nRzQ==", + "version": "24.7.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.7.2.tgz", + "integrity": "sha512-/NbVmcGTP+lj5oa4yiYxxeBjRivKQ5Ns1eSZeB99ExsEQ6rX5XYU1Zy/gGxY/ilqtD4Etx9mKyrPxZRetiahhA==", "dev": true, "peer": true, "requires": { - "undici-types": "~5.26.4" + "undici-types": "~7.14.0" } }, "abbrev": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz", - "integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-3.0.1.tgz", + "integrity": "sha512-AO2ac6pjRB3SJmGJo+v5/aK6Omggp6fsLrs6wN9bd35ulu4cCwaAU9+7ZhXjeqHVkaHThLuzH0nZr0YpCDhygg==", "dev": true }, "acorn": { - "version": "8.8.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", - "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==" + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==" }, "acorn-jsx": { "version": "5.3.2", @@ -6553,37 +7294,19 @@ "requires": {} }, "acorn-walk": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", - "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", - "dev": true - }, - "agent-base": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", - "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", "dev": true, "requires": { - "debug": "^4.3.4" + "acorn": "^8.11.0" } }, - "aggregate-error": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", - "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", - "dev": true, - "requires": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - }, - "dependencies": { - "indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true - } - } + "agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true }, "ajv": { "version": "6.12.6", @@ -6597,10 +7320,13 @@ } }, "ansi-escapes": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-6.2.1.tgz", - "integrity": "sha512-4nJ3yixlEthEJ9Rk4vPcdBRkZvQZlYyu8j4/Mqz5sgIkddmEnH2Yj2ZrnP9S3tQOvSNRUIgVNF/1yPpRAGNRig==", - "dev": true + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.1.1.tgz", + "integrity": "sha512-Zhl0ErHcSRUaVfGUeUdDuLgpkEo8KIFjB4Y9uAc46ScOpdDiU1Dbyplh7qWJeJ/ZHpbyMSM26+X3BySgnIz40Q==", + "dev": true, + "requires": { + "environment": "^1.0.0" + } }, "ansi-regex": { "version": "5.0.1", @@ -6660,9 +7386,9 @@ "dev": true }, "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -6678,74 +7404,48 @@ } }, "c8": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/c8/-/c8-9.1.0.tgz", - "integrity": "sha512-mBWcT5iqNir1zIkzSPyI3NCR9EZCVI3WUD+AVO17MVWTSFNyUueXE82qTeampNtTr+ilN/5Ua3j24LgbCKjDVg==", + "version": "10.1.3", + "resolved": "https://registry.npmjs.org/c8/-/c8-10.1.3.tgz", + "integrity": "sha512-LvcyrOAaOnrrlMpW22n690PUvxiq4Uf9WMhQwNJ9vgagkL/ph1+D4uvjvDA5XCbykrc0sx+ay6pVi9YZ1GnhyA==", "dev": true, "requires": { - "@bcoe/v8-coverage": "^0.2.3", + "@bcoe/v8-coverage": "^1.0.1", "@istanbuljs/schema": "^0.1.3", "find-up": "^5.0.0", "foreground-child": "^3.1.1", "istanbul-lib-coverage": "^3.2.0", "istanbul-lib-report": "^3.0.1", "istanbul-reports": "^3.1.6", - "test-exclude": "^6.0.0", + "test-exclude": "^7.0.1", "v8-to-istanbul": "^9.0.0", "yargs": "^17.7.2", "yargs-parser": "^21.1.1" } }, "cacache": { - "version": "18.0.3", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-18.0.3.tgz", - "integrity": "sha512-qXCd4rh6I07cnDqh8V48/94Tc/WSfj+o3Gn6NZ0aZovS255bUx8O13uKxRFd2eWG0xgsco7+YItQNPaa5E85hg==", + "version": "20.0.1", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-20.0.1.tgz", + "integrity": "sha512-+7LYcYGBYoNqTp1Rv7Ny1YjUo5E0/ftkQtraH3vkfAGgVHc+ouWdC8okAwQgQR7EVIdW6JTzTmhKFwzb+4okAQ==", "dev": true, "requires": { - "@npmcli/fs": "^3.1.0", + "@npmcli/fs": "^4.0.0", "fs-minipass": "^3.0.0", - "glob": "^10.2.2", - "lru-cache": "^10.0.1", + "glob": "^11.0.3", + "lru-cache": "^11.1.0", "minipass": "^7.0.3", "minipass-collect": "^2.0.1", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", - "p-map": "^4.0.0", - "ssri": "^10.0.0", - "tar": "^6.1.11", - "unique-filename": "^3.0.0" + "p-map": "^7.0.2", + "ssri": "^12.0.0", + "unique-filename": "^4.0.0" }, "dependencies": { - "brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0" - } - }, - "glob": { - "version": "10.4.1", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.1.tgz", - "integrity": "sha512-2jelhlq3E4ho74ZyVLN03oKdAZVUa6UDZzFLVH1H7dnoax+y9qyaq8zBkfDIggjniU19z0wU18y16jMB2eyVIw==", - "dev": true, - "requires": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "path-scurry": "^1.11.1" - } - }, - "minimatch": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", - "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", - "dev": true, - "requires": { - "brace-expansion": "^2.0.1" - } + "lru-cache": { + "version": "11.2.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.2.tgz", + "integrity": "sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==", + "dev": true } } }, @@ -6791,9 +7491,9 @@ } }, "chownr": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", - "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", "dev": true }, "chroma-js": { @@ -6801,18 +7501,6 @@ "resolved": "https://registry.npmjs.org/chroma-js/-/chroma-js-2.4.2.tgz", "integrity": "sha512-U9eDw6+wt7V8z5NncY2jJfZa+hUH8XEj8FQHgFJTrUFnJfXYf4Ml4adI2vXZOjqRDpFWtYVWypDfZwnJ+HIR4A==" }, - "ci-info": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", - "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", - "dev": true - }, - "clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "dev": true - }, "cli-boxes": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz", @@ -6829,19 +7517,31 @@ } }, "cli-truncate": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-3.1.0.tgz", - "integrity": "sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-4.0.0.tgz", + "integrity": "sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==", "dev": true, "requires": { "slice-ansi": "^5.0.0", - "string-width": "^5.0.0" + "string-width": "^7.0.0" }, "dependencies": { + "ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true + }, "ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true + }, + "emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", "dev": true }, "slice-ansi": { @@ -6853,6 +7553,26 @@ "ansi-styles": "^6.0.0", "is-fullwidth-code-point": "^4.0.0" } + }, + "string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "requires": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + } + }, + "strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "dev": true, + "requires": { + "ansi-regex": "^6.0.1" + } } } }, @@ -6931,9 +7651,9 @@ "integrity": "sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w==" }, "compress-json": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/compress-json/-/compress-json-3.0.5.tgz", - "integrity": "sha512-HYiJvE0cTIygI9zXqY5fkRr7H3NV3UAME0enzwN5M0JkzMOtUcjSyaH7HxVRzXsn7IIXD0STA9M5jyWkxERSLg==" + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/compress-json/-/compress-json-3.4.0.tgz", + "integrity": "sha512-SxT8uFOacRbP3gfi4aVAulr4KzE933eZWBguVBwuVWv3GTGQAToqpq8rFtdbIVgeCOH7RpPuXKQrPPd087b8uA==" }, "concat-map": { "version": "0.0.1", @@ -6953,9 +7673,9 @@ "dev": true }, "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "requires": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -6963,11 +7683,11 @@ } }, "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "requires": { - "ms": "2.1.2" + "ms": "^2.1.3" } }, "deep-is": { @@ -6981,14 +7701,6 @@ "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", "dev": true }, - "doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "requires": { - "esutils": "^2.0.2" - } - }, "eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -7015,16 +7727,28 @@ "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", "dev": true }, + "environment": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", + "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", + "dev": true + }, "err-code": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", "dev": true }, + "es-toolkit": { + "version": "1.40.0", + "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.40.0.tgz", + "integrity": "sha512-8o6w0KFmU0CiIl0/Q/BCEOabF2IJaELM1T2PWj6e8KqzHv1gdx+7JtFnDwOx1kJH/isJ5NwlDG1nCr1HrRF94Q==", + "dev": true + }, "escalade": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", - "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "dev": true }, "escape-string-regexp": { @@ -7033,74 +7757,69 @@ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==" }, "eslint": { - "version": "8.37.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.37.0.tgz", - "integrity": "sha512-NU3Ps9nI05GUoVMxcZx1J8CNR6xOvUT4jAUMH5+z8lpp3aEdPVCImKw6PWG4PY+Vfkpr+jvMpxs/qoE7wq0sPw==", - "requires": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.4.0", - "@eslint/eslintrc": "^2.0.2", - "@eslint/js": "8.37.0", - "@humanwhocodes/config-array": "^0.11.8", + "version": "9.37.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.37.0.tgz", + "integrity": "sha512-XyLmROnACWqSxiGYArdef1fItQd47weqB7iwtfr9JHwRrqIXZdcFMvvEcL9xHCmL0SNsOvF0c42lWyM1U5dgig==", + "requires": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.0", + "@eslint/config-helpers": "^0.4.0", + "@eslint/core": "^0.16.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.37.0", + "@eslint/plugin-kit": "^0.4.0", + "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", - "ajv": "^6.10.0", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", + "ajv": "^6.12.4", "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", + "cross-spawn": "^7.0.6", "debug": "^4.3.2", - "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.1.1", - "eslint-visitor-keys": "^3.4.0", - "espree": "^9.5.1", - "esquery": "^1.4.2", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", + "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "grapheme-splitter": "^1.0.4", "ignore": "^5.2.0", - "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", - "js-sdsl": "^4.1.4", - "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", - "optionator": "^0.9.1", - "strip-ansi": "^6.0.1", - "strip-json-comments": "^3.1.0", - "text-table": "^0.2.0" + "optionator": "^0.9.3" } }, "eslint-scope": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", - "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", "requires": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "eslint-visitor-keys": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.0.tgz", - "integrity": "sha512-HPpKPUBQcAsZOsHAFwTtIKcYlCje62XB7SEAcxjtmW6TD1WVpkS6i6/hOVtTZIl4zGj/mBqpFVGvaDneik+VoQ==" + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==" }, "espree": { - "version": "9.5.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.1.tgz", - "integrity": "sha512-5yxtHSZXRSW5pvv3hAlXM5+/Oswi1AUFqBmbibKb5s6bp3rGIDkyXU6xCoyuuLhijr4SFwPrXRoZjz0AZDN9tg==", + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", "requires": { - "acorn": "^8.8.0", + "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.0" + "eslint-visitor-keys": "^4.2.1" } }, "esquery": { @@ -7136,9 +7855,9 @@ "dev": true }, "exponential-backoff": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.1.tgz", - "integrity": "sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.3.tgz", + "integrity": "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==", "dev": true }, "fast-deep-equal": { @@ -7156,20 +7875,12 @@ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==" }, - "fastq": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", - "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", - "requires": { - "reusify": "^1.0.4" - } - }, "file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", "requires": { - "flat-cache": "^3.0.4" + "flat-cache": "^4.0.0" } }, "fill-range": { @@ -7191,28 +7902,18 @@ } }, "flat-cache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", "requires": { - "flatted": "^3.1.0", - "rimraf": "^3.0.2" - }, - "dependencies": { - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "requires": { - "glob": "^7.1.3" - } - } + "flatted": "^3.2.9", + "keyv": "^4.5.4" } }, "flatted": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.5.tgz", - "integrity": "sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg==" + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==" }, "flexsearch": { "version": "0.7.43", @@ -7220,11 +7921,11 @@ "integrity": "sha512-c5o/+Um8aqCSOXGcZoqZOm+NqtVwNsvVpWv6lfmSclU954O3wvQKxxK8zj74fPaSJbXpSLTs4PRhh+wnoCXnKg==" }, "foreground-child": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", - "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", "requires": { - "cross-spawn": "^7.0.0", + "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, @@ -7243,11 +7944,6 @@ "minipass": "^7.0.3" } }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" - }, "fsevents": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", @@ -7255,12 +7951,6 @@ "dev": true, "optional": true }, - "function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true - }, "function-loop": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/function-loop/-/function-loop-4.0.0.tgz", @@ -7273,17 +7963,60 @@ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true }, + "get-east-asian-width": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.4.0.tgz", + "integrity": "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==", + "dev": true + }, "glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.3.tgz", + "integrity": "sha512-2Nim7dha1KVkaiF4q6Dj+ngPPMdfvLJEOpZk/jKiUAkqKebpGAWQXAq9z1xu9HKu5lWfqw/FASuccEjyznjPaA==", + "dev": true, "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "foreground-child": "^3.3.1", + "jackspeak": "^4.1.1", + "minimatch": "^10.0.3", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^2.0.0" + }, + "dependencies": { + "jackspeak": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.1.1.tgz", + "integrity": "sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==", + "dev": true, + "requires": { + "@isaacs/cliui": "^8.0.2" + } + }, + "lru-cache": { + "version": "11.2.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.2.tgz", + "integrity": "sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==", + "dev": true + }, + "minimatch": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.3.tgz", + "integrity": "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==", + "dev": true, + "requires": { + "@isaacs/brace-expansion": "^5.0.0" + } + }, + "path-scurry": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", + "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==", + "dev": true, + "requires": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + } + } } }, "glob-parent": { @@ -7295,12 +8028,9 @@ } }, "globals": { - "version": "13.20.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", - "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", - "requires": { - "type-fest": "^0.20.2" - } + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==" }, "graceful-fs": { "version": "4.2.11", @@ -7308,37 +8038,26 @@ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "dev": true }, - "grapheme-splitter": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", - "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==" - }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" }, - "hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, - "requires": { - "function-bind": "^1.1.2" - } - }, - "he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==" - }, "hosted-git-info": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz", - "integrity": "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-9.0.2.tgz", + "integrity": "sha512-M422h7o/BR3rmCQ8UHi7cyyMqKltdP9Uo+J2fXK+RSAY+wTcKOIRyhTuKv4qn+DJf3g+PL890AzId5KZpX+CBg==", "dev": true, "requires": { - "lru-cache": "^10.0.1" + "lru-cache": "^11.1.0" + }, + "dependencies": { + "lru-cache": { + "version": "11.2.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.2.tgz", + "integrity": "sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==", + "dev": true + } } }, "html-escaper": { @@ -7348,9 +8067,9 @@ "dev": true }, "http-cache-semantics": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", - "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", "dev": true }, "http-proxy-agent": { @@ -7364,12 +8083,12 @@ } }, "https-proxy-agent": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz", - "integrity": "sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", "dev": true, "requires": { - "agent-base": "^7.0.2", + "agent-base": "^7.1.2", "debug": "4" } }, @@ -7384,51 +8103,39 @@ } }, "ignore": { - "version": "5.2.4", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", - "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==" + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==" }, "ignore-walk": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-6.0.5.tgz", - "integrity": "sha512-VuuG0wCnjhnylG1ABXT3dAuIpTNDs/G8jlpmwXY03fXoXy/8ZK8/T+hMzt8L4WnrLCJgdybqgPagnF/f97cg3A==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-8.0.0.tgz", + "integrity": "sha512-FCeMZT4NiRQGh+YkeKMtWrOmBgWjHjMJ26WQWrRQyoyzqevdaGSakUaJW5xQYmjLlUVk2qUnCjYVBax9EKKg8A==", "dev": true, "requires": { - "minimatch": "^9.0.0" + "minimatch": "^10.0.3" }, "dependencies": { - "brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0" - } - }, "minimatch": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", - "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.3.tgz", + "integrity": "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==", "dev": true, "requires": { - "brace-expansion": "^2.0.1" + "@isaacs/brace-expansion": "^5.0.0" } } } }, "image-size": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/image-size/-/image-size-1.0.2.tgz", - "integrity": "sha512-xfOoWjceHntRb3qFCrh5ZFORYH8XCdYpASltMhZ/Q0KZiOwjdE/Yl2QCiWdwD+lygV5bMCvauzgu5PxBX/Yerg==", - "requires": { - "queue": "6.0.2" - } + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/image-size/-/image-size-2.0.2.tgz", + "integrity": "sha512-IRqXKlaXwgSMAMtpNzZa1ZAe8m+Sa1770Dhk8VkSsP9LS+iHD62Zd8FQKs8fbPiagBE7BzoFX23cxFnwshpV6w==" }, "import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", "requires": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" @@ -7445,57 +8152,66 @@ "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==", "dev": true }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + "ini": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-5.0.0.tgz", + "integrity": "sha512-+N0ngpO3e7cRUWOJAS7qw0IZIVc6XPrW4MlFBdD066F2L4k1L6ker3hLqSq7iXxU5tgS4WGkIUElWn5vogAEnw==", + "dev": true }, "ink": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/ink/-/ink-4.4.1.tgz", - "integrity": "sha512-rXckvqPBB0Krifk5rn/5LvQGmyXwCUpBfmTwbkQNBY9JY8RSl3b8OftBNEYxg4+SWUhEKcPifgope28uL9inlA==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/ink/-/ink-5.2.1.tgz", + "integrity": "sha512-BqcUyWrG9zq5HIwW6JcfFHsIYebJkWWb4fczNah1goUO0vv5vneIlfwuS85twyJ5hYR/y18FlAYUxrO9ChIWVg==", "dev": true, "requires": { "@alcalzone/ansi-tokenize": "^0.1.3", - "ansi-escapes": "^6.0.0", + "ansi-escapes": "^7.0.0", + "ansi-styles": "^6.2.1", "auto-bind": "^5.0.1", - "chalk": "^5.2.0", + "chalk": "^5.3.0", "cli-boxes": "^3.0.0", "cli-cursor": "^4.0.0", - "cli-truncate": "^3.1.0", + "cli-truncate": "^4.0.0", "code-excerpt": "^4.0.0", + "es-toolkit": "^1.22.0", "indent-string": "^5.0.0", - "is-ci": "^3.0.1", - "is-lower-case": "^2.0.2", - "is-upper-case": "^2.0.2", - "lodash": "^4.17.21", + "is-in-ci": "^1.0.0", "patch-console": "^2.0.0", "react-reconciler": "^0.29.0", "scheduler": "^0.23.0", "signal-exit": "^3.0.7", - "slice-ansi": "^6.0.0", + "slice-ansi": "^7.1.0", "stack-utils": "^2.0.6", - "string-width": "^5.1.2", - "type-fest": "^0.12.0", - "widest-line": "^4.0.1", - "wrap-ansi": "^8.1.0", - "ws": "^8.12.0", - "yoga-wasm-web": "~0.3.3" + "string-width": "^7.2.0", + "type-fest": "^4.27.0", + "widest-line": "^5.0.0", + "wrap-ansi": "^9.0.0", + "ws": "^8.18.0", + "yoga-layout": "~3.2.1" }, "dependencies": { + "ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true + }, + "ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true + }, "chalk": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", - "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "dev": true + }, + "emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", "dev": true }, "signal-exit": { @@ -7504,23 +8220,44 @@ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "dev": true }, - "type-fest": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.12.0.tgz", - "integrity": "sha512-53RyidyjvkGpnWPMF9bQgFtWp+Sl8O2Rp13VavmJgfAP9WWG6q6TkrKU8iyJdnwnfgHI6k2hTlgqH4aSdjoTbg==", - "dev": true + "string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "requires": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + } + }, + "strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "dev": true, + "requires": { + "ansi-regex": "^6.0.1" + } + }, + "wrap-ansi": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "dev": true, + "requires": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + } } } }, "ip-address": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", - "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", - "dev": true, - "requires": { - "jsbn": "1.1.0", - "sprintf-js": "^1.1.3" - } + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz", + "integrity": "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==", + "dev": true }, "is-actual-promise": { "version": "1.0.2", @@ -7537,24 +8274,6 @@ "binary-extensions": "^2.0.0" } }, - "is-ci": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.1.tgz", - "integrity": "sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==", - "dev": true, - "requires": { - "ci-info": "^3.2.0" - } - }, - "is-core-module": { - "version": "2.13.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", - "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", - "dev": true, - "requires": { - "hasown": "^2.0.0" - } - }, "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -7574,47 +8293,24 @@ "is-extglob": "^2.1.1" } }, - "is-lambda": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", - "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==", + "is-in-ci": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-in-ci/-/is-in-ci-1.0.0.tgz", + "integrity": "sha512-eUuAjybVTHMYWm/U+vBO1sY/JOCgoPCXRxzdju0K+K0BiGW0SChEL1MLC0PoCIR1OlPo5YAp8HuQoUlsWEICwg==", "dev": true }, - "is-lower-case": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-lower-case/-/is-lower-case-2.0.2.tgz", - "integrity": "sha512-bVcMJy4X5Og6VZfdOZstSexlEy20Sr0k/p/b2IlQJlfdKAQuMpiv5w2Ccxb8sKdRUNAG1PnHVHjFSdRDVS6NlQ==", - "dev": true, - "requires": { - "tslib": "^2.0.3" - } - }, "is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true }, - "is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==" - }, "is-plain-object": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", "dev": true }, - "is-upper-case": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-upper-case/-/is-upper-case-2.0.2.tgz", - "integrity": "sha512-44pxmxAvnnAOwBg4tHPnkfvgjPwbc5QIsSstNU+YcJ1ovxVzCWpSGosPJOZh/a1tdl81fbgnLc9LLv+x2ywbPQ==", - "dev": true, - "requires": { - "tslib": "^2.0.3" - } - }, "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -7638,9 +8334,9 @@ } }, "istanbul-reports": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", - "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", "dev": true, "requires": { "html-escaper": "^2.0.0", @@ -7656,11 +8352,6 @@ "@pkgjs/parseargs": "^0.11.0" } }, - "js-sdsl": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.4.0.tgz", - "integrity": "sha512-FfVSdx6pJ41Oa+CF7RDaFmTnCaFhua+SNYQX74riGOpl96x+2jQCqEfQ2bnXu/5DPCqlRuiqyvTJM0Qjz26IVg==" - }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -7680,16 +8371,15 @@ "resolved": "https://registry.npmjs.org/jsbi/-/jsbi-4.3.0.tgz", "integrity": "sha512-SnZNcinB4RIcnEyZqFPdGPVgrg2AcnykiBy0sHVJQKHYeaLUvi3Exj+iaPpLnFVkDPZIV4U0yvgC9/R4uEAZ9g==" }, - "jsbn": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", - "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==", - "dev": true + "json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==" }, "json-parse-even-better-errors": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.2.tgz", - "integrity": "sha512-fi0NG4bPjCHunUJffmLd0gxssIgkNmArMvis4iNah6Owg1MCJjWhEcDLmsK6iGkJq3tHwbDkTlce70/tmXN4cQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-4.0.0.tgz", + "integrity": "sha512-lR4MXjGNgkJc7tkQ97kb2nuEMnNCyU//XYVH0MKTGcXEiSudQ5MKGKen3C5QubYy0vmq+JGitUg92uuywGEwIA==", "dev": true }, "json-schema-traverse": { @@ -7708,6 +8398,14 @@ "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==", "dev": true }, + "keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "requires": { + "json-buffer": "3.0.1" + } + }, "levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -7725,12 +8423,6 @@ "p-locate": "^5.0.0" } }, - "lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true - }, "lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -7766,29 +8458,82 @@ "dev": true }, "make-fetch-happen": { - "version": "13.0.1", - "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-13.0.1.tgz", - "integrity": "sha512-cKTUFc/rbKUd/9meOvgrpJ2WrNzymt6jfRDdwg5UCnVzv9dTpEj9JS5m3wtziXVCjluIXyL8pcaukYqezIzZQA==", + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-14.0.3.tgz", + "integrity": "sha512-QMjGbFTP0blj97EeidG5hk/QhKQ3T4ICckQGLgz38QF7Vgbk6e6FTARN8KhKxyBbWn8R0HU+bnw8aSoFPD4qtQ==", "dev": true, "requires": { - "@npmcli/agent": "^2.0.0", - "cacache": "^18.0.0", + "@npmcli/agent": "^3.0.0", + "cacache": "^19.0.1", "http-cache-semantics": "^4.1.1", - "is-lambda": "^1.0.1", "minipass": "^7.0.2", - "minipass-fetch": "^3.0.0", + "minipass-fetch": "^4.0.0", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", - "negotiator": "^0.6.3", - "proc-log": "^4.2.0", + "negotiator": "^1.0.0", + "proc-log": "^5.0.0", "promise-retry": "^2.0.1", - "ssri": "^10.0.0" + "ssri": "^12.0.0" + }, + "dependencies": { + "brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "cacache": { + "version": "19.0.1", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-19.0.1.tgz", + "integrity": "sha512-hdsUxulXCi5STId78vRVYEtDAjq99ICAUktLTeTYsLoTE6Z8dS0c8pWNCxwdrk9YfJeobDZc2Y186hD/5ZQgFQ==", + "dev": true, + "requires": { + "@npmcli/fs": "^4.0.0", + "fs-minipass": "^3.0.0", + "glob": "^10.2.2", + "lru-cache": "^10.0.1", + "minipass": "^7.0.3", + "minipass-collect": "^2.0.1", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "p-map": "^7.0.2", + "ssri": "^12.0.0", + "tar": "^7.4.3", + "unique-filename": "^4.0.0" + } + }, + "glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "requires": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + } + }, + "minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + } } }, "marked": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/marked/-/marked-10.0.0.tgz", - "integrity": "sha512-YiGcYcWj50YrwBgNzFoYhQ1hT6GmQbFG8SksnYJX1z4BXTHSOrz1GB5/Jm2yQvMg4nN1FHP4M6r03R10KrVUiA==" + "version": "16.4.1", + "resolved": "https://registry.npmjs.org/marked/-/marked-16.4.1.tgz", + "integrity": "sha512-ntROs7RaN3EvWfy3EZi14H4YxmT6A5YvywfhO+0pm+cH/dnSQRmdAmoFIc3B9aiwTehyk7pESH4ofyBY+V5hZg==" }, "mimic-fn": { "version": "2.1.0", @@ -7819,15 +8564,15 @@ } }, "minipass-fetch": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-3.0.5.tgz", - "integrity": "sha512-2N8elDQAtSnFV0Dk7gt15KHsS0Fyz6CbYZ360h0WTYV1Ty46li3rAXVOQj1THMNLdmrD9Vt5pBPtWtVkpwGBqg==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-4.0.1.tgz", + "integrity": "sha512-j7U11C5HXigVuutxebFadoYBbd7VSdZWggSe64NVdvWNBqGAiXPL2QVCehjmw7lY1oF9gOllYbORh+hiNgfPgQ==", "dev": true, "requires": { "encoding": "^0.1.13", "minipass": "^7.0.3", "minipass-sized": "^1.0.3", - "minizlib": "^2.1.2" + "minizlib": "^3.0.1" } }, "minipass-flush": { @@ -7847,27 +8592,12 @@ "requires": { "yallist": "^4.0.0" } - } - } - }, - "minipass-json-stream": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minipass-json-stream/-/minipass-json-stream-1.0.1.tgz", - "integrity": "sha512-ODqY18UZt/I8k+b7rl2AENgbWE8IDYam+undIJONvigAz8KR5GWblsFTEfQs0WODsjbSXWlm+JHEv8Gr6Tfdbg==", - "dev": true, - "requires": { - "jsonparse": "^1.3.1", - "minipass": "^3.0.0" - }, - "dependencies": { - "minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true } } }, @@ -7888,6 +8618,12 @@ "requires": { "yallist": "^4.0.0" } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true } } }, @@ -7908,28 +8644,22 @@ "requires": { "yallist": "^4.0.0" } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true } } }, "minizlib": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", - "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", + "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", "dev": true, "requires": { - "minipass": "^3.0.0", - "yallist": "^4.0.0" - }, - "dependencies": { - "minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - } + "minipass": "^7.1.2" } }, "mkdirp": { @@ -7939,14 +8669,14 @@ "dev": true }, "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, "msgpackr": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.10.2.tgz", - "integrity": "sha512-L60rsPynBvNE+8BWipKKZ9jHcSGbtyJYIwjRq0VrIvQ08cRjntGXJYW/tmciZ2IHWIY8WEW32Qa2xbh5+SKBZA==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.11.1.tgz", + "integrity": "sha512-P6RWMjddZDjSndqKd5XhmHhpnxohGEj4+55w9tSACji7X/P6gTP/KzYGj7/U2RfE2D3HONS6Y63eGkuFXJEMtA==", "requires": { "msgpackr-extract": "^3.0.2" } @@ -7972,76 +8702,39 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==" }, "negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", "dev": true }, "node-gyp": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-10.1.0.tgz", - "integrity": "sha512-B4J5M1cABxPc5PwfjhbV5hoy2DP9p8lFXASnEN6hugXOa61416tnTZ29x9sSwAd0o99XNIcpvDDy1swAExsVKA==", + "version": "11.5.0", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-11.5.0.tgz", + "integrity": "sha512-ra7Kvlhxn5V9Slyus0ygMa2h+UqExPqUIkfk7Pc8QTLT956JLSy51uWFwHtIYy0vI8cB4BDhc/S03+880My/LQ==", "dev": true, "requires": { "env-paths": "^2.2.0", "exponential-backoff": "^3.1.1", - "glob": "^10.3.10", "graceful-fs": "^4.2.6", - "make-fetch-happen": "^13.0.0", - "nopt": "^7.0.0", - "proc-log": "^3.0.0", + "make-fetch-happen": "^14.0.3", + "nopt": "^8.0.0", + "proc-log": "^5.0.0", "semver": "^7.3.5", - "tar": "^6.1.2", - "which": "^4.0.0" + "tar": "^7.4.3", + "tinyglobby": "^0.2.12", + "which": "^5.0.0" }, "dependencies": { - "brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0" - } - }, - "glob": { - "version": "10.4.1", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.1.tgz", - "integrity": "sha512-2jelhlq3E4ho74ZyVLN03oKdAZVUa6UDZzFLVH1H7dnoax+y9qyaq8zBkfDIggjniU19z0wU18y16jMB2eyVIw==", - "dev": true, - "requires": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "path-scurry": "^1.11.1" - } - }, "isexe": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", "dev": true }, - "minimatch": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", - "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", - "dev": true, - "requires": { - "brace-expansion": "^2.0.1" - } - }, - "proc-log": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-3.0.0.tgz", - "integrity": "sha512-++Vn7NS4Xf9NacaU9Xq3URUuqZETPsf8L4j5/ckhaRYsfPeRyzGw+iDjFhV/Jr3uNmTvvddEJFWh5R1gRgUH8A==", - "dev": true - }, "which": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", - "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", + "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", "dev": true, "requires": { "isexe": "^3.1.1" @@ -8056,24 +8749,12 @@ "optional": true }, "nopt": { - "version": "7.2.1", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.1.tgz", - "integrity": "sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==", - "dev": true, - "requires": { - "abbrev": "^2.0.0" - } - }, - "normalize-package-data": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-6.0.1.tgz", - "integrity": "sha512-6rvCfeRW+OEZagAB4lMLSNuTNYZWLVtKccK79VSTf//yTY5VOCgcpH80O+bZK8Neps7pUnd5G+QlMg1yV/2iZQ==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-8.1.0.tgz", + "integrity": "sha512-ieGu42u/Qsa4TFktmaKEwM6MQH0pOWnaB3htzh0JRtx84+Mebc0cbZYN5bC+6WTZ4+77xrL9Pn5m7CV6VIkV7A==", "dev": true, "requires": { - "hosted-git-info": "^7.0.0", - "is-core-module": "^2.8.1", - "semver": "^7.3.5", - "validate-npm-package-license": "^3.0.4" + "abbrev": "^3.0.0" } }, "normalize-path": { @@ -8083,84 +8764,117 @@ "dev": true }, "npm-bundled": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-3.0.1.tgz", - "integrity": "sha512-+AvaheE/ww1JEwRHOrn4WHNzOxGtVp+adrg2AeZS/7KuxGUYFuBta98wYpfHBbJp6Tg6j1NKSEVHNcfZzJHQwQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-4.0.0.tgz", + "integrity": "sha512-IxaQZDMsqfQ2Lz37VvyyEtKLe8FsRZuysmedy/N06TU1RyVppYKXrO4xIhR0F+7ubIBox6Q7nir6fQI3ej39iA==", "dev": true, "requires": { - "npm-normalize-package-bin": "^3.0.0" + "npm-normalize-package-bin": "^4.0.0" } }, "npm-install-checks": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-6.3.0.tgz", - "integrity": "sha512-W29RiK/xtpCGqn6f3ixfRYGk+zRyr+Ew9F2E20BfXxT5/euLdA/Nm7fO7OeTGuAmTs30cpgInyJ0cYe708YTZw==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-7.1.2.tgz", + "integrity": "sha512-z9HJBCYw9Zr8BqXcllKIs5nI+QggAImbBdHphOzVYrz2CB4iQ6FzWyKmlqDZua+51nAu7FcemlbTc9VgQN5XDQ==", "dev": true, "requires": { "semver": "^7.1.1" } }, "npm-normalize-package-bin": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-3.0.1.tgz", - "integrity": "sha512-dMxCf+zZ+3zeQZXKxmyuCKlIDPGuv8EF940xbkC4kQVDTtqoh6rJFO+JTKSA6/Rwi0getWmtuy4Itup0AMcaDQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-4.0.0.tgz", + "integrity": "sha512-TZKxPvItzai9kN9H/TkmCtx/ZN/hvr3vUycjlfmH0ootY9yFBzNOpiXAdIn1Iteqsvk4lQn6B5PTrt+n6h8k/w==", "dev": true }, "npm-package-arg": { - "version": "11.0.2", - "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-11.0.2.tgz", - "integrity": "sha512-IGN0IAwmhDJwy13Wc8k+4PEbTPhpJnMtfR53ZbOyjkvmEcLS4nCwp6mvMWjS5sUjeiW3mpx6cHmuhKEu9XmcQw==", + "version": "13.0.1", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-13.0.1.tgz", + "integrity": "sha512-6zqls5xFvJbgFjB1B2U6yITtyGBjDBORB7suI4zA4T/sZ1OmkMFlaQSNB/4K0LtXNA1t4OprAFxPisadK5O2ag==", "dev": true, "requires": { - "hosted-git-info": "^7.0.0", - "proc-log": "^4.0.0", + "hosted-git-info": "^9.0.0", + "proc-log": "^5.0.0", "semver": "^7.3.5", - "validate-npm-package-name": "^5.0.0" + "validate-npm-package-name": "^6.0.0" } }, "npm-packlist": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-8.0.2.tgz", - "integrity": "sha512-shYrPFIS/JLP4oQmAwDyk5HcyysKW8/JLTEA32S0Z5TzvpaeeX2yMFfoK1fjEBnCBvVyIB/Jj/GBFdm0wsgzbA==", + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-10.0.2.tgz", + "integrity": "sha512-DrIWNiWT0FTdDRjGOYfEEZUNe1IzaSZ+up7qBTKnrQDySpdmuOQvytrqQlpK5QrCA4IThMvL4wTumqaa1ZvVIQ==", "dev": true, "requires": { - "ignore-walk": "^6.0.4" + "ignore-walk": "^8.0.0", + "proc-log": "^5.0.0" } }, "npm-pick-manifest": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-9.0.1.tgz", - "integrity": "sha512-Udm1f0l2nXb3wxDpKjfohwgdFUSV50UVwzEIpDXVsbDMXVIEF81a/i0UhuQbhrPMMmdiq3+YMFLFIRVLs3hxQw==", + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-11.0.1.tgz", + "integrity": "sha512-HnU7FYSWbo7dTVHtK0G+BXbZ0aIfxz/aUCVLN0979Ec6rGUX5cJ6RbgVx5fqb5G31ufz+BVFA7y1SkRTPVNoVQ==", "dev": true, "requires": { - "npm-install-checks": "^6.0.0", - "npm-normalize-package-bin": "^3.0.0", - "npm-package-arg": "^11.0.0", + "npm-install-checks": "^7.1.0", + "npm-normalize-package-bin": "^4.0.0", + "npm-package-arg": "^13.0.0", "semver": "^7.3.5" } }, "npm-registry-fetch": { - "version": "16.2.1", - "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-16.2.1.tgz", - "integrity": "sha512-8l+7jxhim55S85fjiDGJ1rZXBWGtRLi1OSb4Z3BPLObPuIaeKRlPRiYMSHU4/81ck3t71Z+UwDDl47gcpmfQQA==", + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-19.0.0.tgz", + "integrity": "sha512-DFxSAemHUwT/POaXAOY4NJmEWBPB0oKbwD6FFDE9hnt1nORkt/FXvgjD4hQjoKoHw9u0Ezws9SPXwV7xE/Gyww==", "dev": true, "requires": { - "@npmcli/redact": "^1.1.0", - "make-fetch-happen": "^13.0.0", + "@npmcli/redact": "^3.0.0", + "jsonparse": "^1.3.1", + "make-fetch-happen": "^15.0.0", "minipass": "^7.0.2", - "minipass-fetch": "^3.0.0", - "minipass-json-stream": "^1.0.1", - "minizlib": "^2.1.2", - "npm-package-arg": "^11.0.0", - "proc-log": "^4.0.0" - } - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "requires": { - "wrappy": "1" + "minipass-fetch": "^4.0.0", + "minizlib": "^3.0.1", + "npm-package-arg": "^13.0.0", + "proc-log": "^5.0.0" + }, + "dependencies": { + "@npmcli/agent": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-4.0.0.tgz", + "integrity": "sha512-kAQTcEN9E8ERLVg5AsGwLNoFb+oEG6engbqAU2P43gD4JEIkNGMHdVQ096FsOAAYpZPB0RSt0zgInKIAS1l5QA==", + "dev": true, + "requires": { + "agent-base": "^7.1.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.1", + "lru-cache": "^11.2.1", + "socks-proxy-agent": "^8.0.3" + } + }, + "lru-cache": { + "version": "11.2.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.2.tgz", + "integrity": "sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==", + "dev": true + }, + "make-fetch-happen": { + "version": "15.0.2", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-15.0.2.tgz", + "integrity": "sha512-sI1NY4lWlXBAfjmCtVWIIpBypbBdhHtcjnwnv+gtCnsaOffyFil3aidszGC8hgzJe+fT1qix05sWxmD/Bmf/oQ==", + "dev": true, + "requires": { + "@npmcli/agent": "^4.0.0", + "cacache": "^20.0.1", + "http-cache-semantics": "^4.1.1", + "minipass": "^7.0.2", + "minipass-fetch": "^4.0.0", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^1.0.0", + "proc-log": "^5.0.0", + "promise-retry": "^2.0.1", + "ssri": "^12.0.0" + } + } } }, "onetime": { @@ -8179,16 +8893,16 @@ "dev": true }, "optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", "requires": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", - "word-wrap": "^1.2.3" + "word-wrap": "^1.2.5" } }, "p-limit": { @@ -8208,38 +8922,40 @@ } }, "p-map": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", - "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", - "dev": true, - "requires": { - "aggregate-error": "^3.0.0" - } + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.3.tgz", + "integrity": "sha512-VkndIv2fIB99swvQoA65bm+fsmt6UNdGeIB0oxBs+WhAhdh08QA04JXpI7rbB9r08/nkbysKoya9rtDERYOYMA==", + "dev": true + }, + "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==", + "dev": true }, "pacote": { - "version": "17.0.7", - "resolved": "https://registry.npmjs.org/pacote/-/pacote-17.0.7.tgz", - "integrity": "sha512-sgvnoUMlkv9xHwDUKjKQFXVyUi8dtJGKp3vg6sYy+TxbDic5RjZCHF3ygv0EJgNRZ2GfRONjlKPUfokJ9lDpwQ==", + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/pacote/-/pacote-21.0.3.tgz", + "integrity": "sha512-itdFlanxO0nmQv4ORsvA9K1wv40IPfB9OmWqfaJWvoJ30VKyHsqNgDVeG+TVhI7Gk7XW8slUy7cA9r6dF5qohw==", "dev": true, "requires": { - "@npmcli/git": "^5.0.0", - "@npmcli/installed-package-contents": "^2.0.1", - "@npmcli/promise-spawn": "^7.0.0", - "@npmcli/run-script": "^7.0.0", - "cacache": "^18.0.0", + "@npmcli/git": "^7.0.0", + "@npmcli/installed-package-contents": "^3.0.0", + "@npmcli/package-json": "^7.0.0", + "@npmcli/promise-spawn": "^8.0.0", + "@npmcli/run-script": "^10.0.0", + "cacache": "^20.0.0", "fs-minipass": "^3.0.0", "minipass": "^7.0.2", - "npm-package-arg": "^11.0.0", - "npm-packlist": "^8.0.0", - "npm-pick-manifest": "^9.0.0", - "npm-registry-fetch": "^16.0.0", - "proc-log": "^4.0.0", + "npm-package-arg": "^13.0.0", + "npm-packlist": "^10.0.1", + "npm-pick-manifest": "^11.0.1", + "npm-registry-fetch": "^19.0.0", + "proc-log": "^5.0.0", "promise-retry": "^2.0.1", - "read-package-json": "^7.0.0", - "read-package-json-fast": "^3.0.0", - "sigstore": "^2.2.0", - "ssri": "^10.0.0", - "tar": "^6.1.11" + "sigstore": "^4.0.0", + "ssri": "^12.0.0", + "tar": "^7.4.3" } }, "parent-module": { @@ -8261,11 +8977,6 @@ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" - }, "path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -8287,15 +8998,15 @@ "dev": true }, "pirates": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", - "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", "dev": true }, "polite-json": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/polite-json/-/polite-json-4.0.1.tgz", - "integrity": "sha512-8LI5ZeCPBEb4uBbcYKNVwk4jgqNx1yHReWoW4H4uUihWlSqZsUDfSITrRhjliuPgxsNPFhNSudGO2Zu4cbWinQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/polite-json/-/polite-json-5.0.0.tgz", + "integrity": "sha512-OLS/0XeUAcE8a2fdwemNja+udKgXNnY6yKVIXqAD2zVRx1KvY6Ato/rZ2vdzbxqYwPW0u6SCNC/bAMPNzpzxbw==", "dev": true }, "prelude-ls": { @@ -8304,9 +9015,9 @@ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==" }, "prismjs": { - "version": "1.29.0", - "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.29.0.tgz", - "integrity": "sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==", + "version": "1.30.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz", + "integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==", "dev": true }, "prismjs-terminal": { @@ -8321,34 +9032,28 @@ }, "dependencies": { "chalk": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", - "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", "dev": true } } }, "proc-log": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-4.2.0.tgz", - "integrity": "sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-5.0.0.tgz", + "integrity": "sha512-Azwzvl90HaF0aCz1JrDdXQykFakSSNPaPoiZ9fm5qJIMHioDZEi7OAdRwSm6rSoPtY3Qutnm3L7ogmg3dc+wbQ==", "dev": true }, "process-on-spawn": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/process-on-spawn/-/process-on-spawn-1.0.0.tgz", - "integrity": "sha512-1WsPDsUSMmZH5LeMLegqkPDrsGgsWwk1Exipy2hvB0o/F0ASzbpIctSCcZIK1ykJvtTJULEH+20WOFjMvGnCTg==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/process-on-spawn/-/process-on-spawn-1.1.0.tgz", + "integrity": "sha512-JOnOPQ/8TZgjs1JIH/m9ni7FfimjNa/PRx7y/Wb5qdItsnhO0jE4AT7fC0HjC28DUQWDr50dwSYZLdRMlqDq3Q==", "dev": true, "requires": { "fromentries": "^1.2.0" } }, - "promise-inflight": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", - "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", - "dev": true - }, "promise-retry": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", @@ -8360,22 +9065,9 @@ } }, "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" - }, - "queue": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/queue/-/queue-6.0.2.tgz", - "integrity": "sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==", - "requires": { - "inherits": "~2.0.3" - } - }, - "queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==" + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==" }, "react": { "version": "18.3.1", @@ -8424,61 +9116,6 @@ "scheduler": "^0.23.2" } }, - "read-package-json": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/read-package-json/-/read-package-json-7.0.1.tgz", - "integrity": "sha512-8PcDiZ8DXUjLf687Ol4BR8Bpm2umR7vhoZOzNRt+uxD9GpBh/K+CAAALVIiYFknmvlmyg7hM7BSNUXPaCCqd0Q==", - "dev": true, - "requires": { - "glob": "^10.2.2", - "json-parse-even-better-errors": "^3.0.0", - "normalize-package-data": "^6.0.0", - "npm-normalize-package-bin": "^3.0.0" - }, - "dependencies": { - "brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0" - } - }, - "glob": { - "version": "10.4.1", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.1.tgz", - "integrity": "sha512-2jelhlq3E4ho74ZyVLN03oKdAZVUa6UDZzFLVH1H7dnoax+y9qyaq8zBkfDIggjniU19z0wU18y16jMB2eyVIw==", - "dev": true, - "requires": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "path-scurry": "^1.11.1" - } - }, - "minimatch": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", - "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", - "dev": true, - "requires": { - "brace-expansion": "^2.0.1" - } - } - } - }, - "read-package-json-fast": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/read-package-json-fast/-/read-package-json-fast-3.0.2.tgz", - "integrity": "sha512-0J+Msgym3vrLOUB3hzQCuZHII0xkNGCtz/HJH9xZshwv9DbDwkw1KaE3gx/e2J5rpEY5rtOy6cyhKOPrkP7FZw==", - "dev": true, - "requires": { - "json-parse-even-better-errors": "^3.0.0", - "npm-normalize-package-bin": "^3.0.0" - } - }, "readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -8500,46 +9137,13 @@ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==" }, "resolve-import": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/resolve-import/-/resolve-import-1.4.5.tgz", - "integrity": "sha512-HXb4YqODuuXT7Icq1Z++0g2JmhgbUHSs3VT2xR83gqvAPUikYT2Xk+562KHQgiaNkbBOlPddYrDLsC44qQggzw==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/resolve-import/-/resolve-import-2.0.0.tgz", + "integrity": "sha512-jpKjLibLuc8D1XEV2+7zb0aqN7I8d12u89g/v6IsgCzdVlccMQJq4TKkPw5fbhHdxhm7nbVtN+KvOTnjFf+nEA==", "dev": true, "requires": { - "glob": "^10.3.3", - "walk-up-path": "^3.0.1" - }, - "dependencies": { - "brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0" - } - }, - "glob": { - "version": "10.4.1", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.1.tgz", - "integrity": "sha512-2jelhlq3E4ho74ZyVLN03oKdAZVUa6UDZzFLVH1H7dnoax+y9qyaq8zBkfDIggjniU19z0wU18y16jMB2eyVIw==", - "dev": true, - "requires": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "path-scurry": "^1.11.1" - } - }, - "minimatch": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", - "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", - "dev": true, - "requires": { - "brace-expansion": "^2.0.1" - } - } + "glob": "^11.0.0", + "walk-up-path": "^4.0.0" } }, "restore-cursor": { @@ -8566,11 +9170,6 @@ "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", "dev": true }, - "reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==" - }, "rimraf": { "version": "5.0.7", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.7.tgz", @@ -8580,9 +9179,9 @@ }, "dependencies": { "brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "requires": { "balanced-match": "^1.0.0" } @@ -8609,14 +9208,6 @@ } } }, - "run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "requires": { - "queue-microtask": "^1.2.2" - } - }, "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -8634,9 +9225,9 @@ } }, "semver": { - "version": "7.6.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", - "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", "dev": true }, "shebang-command": { @@ -8658,34 +9249,43 @@ "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==" }, "sigstore": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/sigstore/-/sigstore-2.3.1.tgz", - "integrity": "sha512-8G+/XDU8wNsJOQS5ysDVO0Etg9/2uA5gR9l4ZwijjlwxBcrU6RPfwi2+jJmbP+Ap1Hlp/nVAaEO4Fj22/SL2gQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/sigstore/-/sigstore-4.0.0.tgz", + "integrity": "sha512-Gw/FgHtrLM9WP8P5lLcSGh9OQcrTruWCELAiS48ik1QbL0cH+dfjomiRTUE9zzz+D1N6rOLkwXUvVmXZAsNE0Q==", "dev": true, "requires": { - "@sigstore/bundle": "^2.3.2", - "@sigstore/core": "^1.0.0", - "@sigstore/protobuf-specs": "^0.3.2", - "@sigstore/sign": "^2.3.2", - "@sigstore/tuf": "^2.3.4", - "@sigstore/verify": "^1.2.1" + "@sigstore/bundle": "^4.0.0", + "@sigstore/core": "^3.0.0", + "@sigstore/protobuf-specs": "^0.5.0", + "@sigstore/sign": "^4.0.0", + "@sigstore/tuf": "^4.0.0", + "@sigstore/verify": "^3.0.0" } }, "slice-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-6.0.0.tgz", - "integrity": "sha512-6bn4hRfkTvDfUoEQYkERg0BVF1D0vrX9HEkMl08uDiNWvVvjylLHvZFZWkDo6wjT8tUctbYl1nCOuE66ZTaUtA==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.2.tgz", + "integrity": "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==", "dev": true, "requires": { "ansi-styles": "^6.2.1", - "is-fullwidth-code-point": "^4.0.0" + "is-fullwidth-code-point": "^5.0.0" }, "dependencies": { "ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", "dev": true + }, + "is-fullwidth-code-point": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz", + "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==", + "dev": true, + "requires": { + "get-east-asian-width": "^1.3.1" + } } } }, @@ -8696,24 +9296,24 @@ "dev": true }, "socks": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.3.tgz", - "integrity": "sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==", + "version": "2.8.7", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz", + "integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==", "dev": true, "requires": { - "ip-address": "^9.0.5", + "ip-address": "^10.0.1", "smart-buffer": "^4.2.0" } }, "socks-proxy-agent": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.3.tgz", - "integrity": "sha512-VNegTZKhuGq5vSD6XNKlbqWhyt/40CgoEw8XxD6dhnm8Jq9IEa3nIa4HwnM8XOqU0CdB0BwWVXusqiFXfHB3+A==", + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", + "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", "dev": true, "requires": { - "agent-base": "^7.1.1", + "agent-base": "^7.1.2", "debug": "^4.3.4", - "socks": "^2.7.1" + "socks": "^2.8.3" } }, "spdx-correct": { @@ -8743,21 +9343,15 @@ } }, "spdx-license-ids": { - "version": "3.0.18", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.18.tgz", - "integrity": "sha512-xxRs31BqRYHwiMzudOrpSiHtZ8i/GeionCBDSilhYRj+9gIcI8wCZTlXZKu9vZIVqViP3dcp9qE5G6AlIaD+TQ==", - "dev": true - }, - "sprintf-js": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", - "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", + "version": "3.0.22", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.22.tgz", + "integrity": "sha512-4PRT4nh1EImPbt2jASOKHX7PB7I+e4IWNLvkKFDxNhJlfjbYlleYQh285Z/3mPTHSAK/AvdMmw5BNNuYH8ShgQ==", "dev": true }, "ssri": { - "version": "10.0.6", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-10.0.6.tgz", - "integrity": "sha512-MGrFH9Z4NP9Iyhqn16sDtBpRRNJ0Y2hNa6D65h736fVSaPCHr4DM4sWUNvVaSuC+0OBGhwsrydQwmgfg5LncqQ==", + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-12.0.0.tgz", + "integrity": "sha512-S7iGNosepx9RadX82oimUkvr0Ct7IjJbEbs4mJcTxst8um95J3sDYU1RBEOvdu6oL1Wek2ODI5i4MAw+dZ6cAQ==", "dev": true, "requires": { "minipass": "^7.0.3" @@ -8790,15 +9384,15 @@ }, "dependencies": { "ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", "dev": true }, "strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", "dev": true, "requires": { "ansi-regex": "^6.0.1" @@ -8888,143 +9482,105 @@ } }, "sync-content": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/sync-content/-/sync-content-1.0.2.tgz", - "integrity": "sha512-znd3rYiiSxU3WteWyS9a6FXkTA/Wjk8WQsOyzHbineeL837dLn3DA4MRhsIX3qGcxDMH6+uuFV4axztssk7wEQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/sync-content/-/sync-content-2.0.1.tgz", + "integrity": "sha512-NI1mo514yFhr8pV/5Etvgh+pSBUIpoAKoiBIUwALVlQQNAwb40bTw8hhPFaip/dvv0GhpHVOq0vq8iY02ppLTg==", "dev": true, "requires": { - "glob": "^10.2.6", + "glob": "^11.0.0", "mkdirp": "^3.0.1", - "path-scurry": "^1.9.2", - "rimraf": "^5.0.1" + "path-scurry": "^2.0.0", + "rimraf": "^6.0.0", + "tshy": "^3.0.0" }, "dependencies": { - "brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0" - } + "lru-cache": { + "version": "11.2.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.2.tgz", + "integrity": "sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==", + "dev": true }, - "glob": { - "version": "10.4.1", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.1.tgz", - "integrity": "sha512-2jelhlq3E4ho74ZyVLN03oKdAZVUa6UDZzFLVH1H7dnoax+y9qyaq8zBkfDIggjniU19z0wU18y16jMB2eyVIw==", + "path-scurry": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", + "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==", "dev": true, "requires": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "path-scurry": "^1.11.1" + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" } }, - "minimatch": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", - "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", + "rimraf": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-6.0.1.tgz", + "integrity": "sha512-9dkvaxAsk/xNXSJzMgFqqMCuFgt2+KsOFek3TMLfo8NCPfWpBmqwyNn5Y+NX56QUYfCtsyhF3ayiboEoUmJk/A==", "dev": true, "requires": { - "brace-expansion": "^2.0.1" + "glob": "^11.0.0", + "package-json-from-dist": "^1.0.0" } } } }, "tap": { - "version": "19.0.2", - "resolved": "https://registry.npmjs.org/tap/-/tap-19.0.2.tgz", - "integrity": "sha512-SRGulk1RKlVuYtnPeephj+xyE0sG9CvGlKYP4lymBZykLtkwBPnEBjQ2iQmLX5z0BFEMfKh8G4bvZkhoSJb3kg==", - "dev": true, - "requires": { - "@tapjs/after": "1.1.24", - "@tapjs/after-each": "2.0.1", - "@tapjs/asserts": "2.0.1", - "@tapjs/before": "2.0.1", - "@tapjs/before-each": "2.0.1", - "@tapjs/core": "2.0.1", - "@tapjs/filter": "2.0.1", - "@tapjs/fixture": "2.0.1", - "@tapjs/intercept": "2.0.1", - "@tapjs/mock": "2.0.1", - "@tapjs/node-serialize": "2.0.1", - "@tapjs/run": "2.0.2", - "@tapjs/snapshot": "2.0.1", - "@tapjs/spawn": "2.0.1", - "@tapjs/stdin": "2.0.1", - "@tapjs/test": "2.0.1", - "@tapjs/typescript": "1.4.6", - "@tapjs/worker": "2.0.1", - "resolve-import": "^1.4.5" + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/tap/-/tap-21.1.1.tgz", + "integrity": "sha512-WQQkoJw2LbusXPq9d6A3N4SHCpiog1AbjSVyNlqRh6uiCrtra24bZRCURX8cgBjKV4W22dcRevvhMp24+N/oVg==", + "dev": true, + "requires": { + "@tapjs/after": "3.0.2", + "@tapjs/after-each": "4.0.2", + "@tapjs/asserts": "4.0.2", + "@tapjs/before": "4.0.2", + "@tapjs/before-each": "4.0.2", + "@tapjs/chdir": "3.0.2", + "@tapjs/core": "4.0.2", + "@tapjs/filter": "4.0.2", + "@tapjs/fixture": "4.0.2", + "@tapjs/intercept": "4.0.2", + "@tapjs/mock": "4.0.2", + "@tapjs/node-serialize": "4.0.2", + "@tapjs/run": "4.0.3", + "@tapjs/snapshot": "4.0.2", + "@tapjs/spawn": "4.0.2", + "@tapjs/stdin": "4.0.2", + "@tapjs/test": "4.0.2", + "@tapjs/typescript": "3.1.1", + "@tapjs/worker": "4.0.2", + "resolve-import": "2" } }, "tap-parser": { - "version": "16.0.1", - "resolved": "https://registry.npmjs.org/tap-parser/-/tap-parser-16.0.1.tgz", - "integrity": "sha512-vKianJzSSzLkJ3bHBwzvZDDRi9yGMwkRANJxwPAjAue50owB8rlluYySmTN4tZVH0nsh6stvrQbg9kuCL5svdg==", + "version": "18.0.1", + "resolved": "https://registry.npmjs.org/tap-parser/-/tap-parser-18.0.1.tgz", + "integrity": "sha512-Jcu51jzPCDXzuFvyCDGRfJWm2mJ0yRLf+aqWINk4P03HM9uyrq2SLavn26N8qwVz//GZysNt3DQNvKrYjidB3g==", "dev": true, "requires": { "events-to-array": "^2.0.3", - "tap-yaml": "2.2.2" + "tap-yaml": "4.0.1" } }, "tap-yaml": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/tap-yaml/-/tap-yaml-2.2.2.tgz", - "integrity": "sha512-MWG4OpAKtNoNVjCz/BqlDJiwTM99tiHRhHPS4iGOe1ZS0CgM4jSFH92lthSFvvy4EdDjQZDV7uYqUFlU9JuNhw==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/tap-yaml/-/tap-yaml-4.0.1.tgz", + "integrity": "sha512-2D5FSjxSP8v3LCh3N1KZa+FoIaVh2f7bFaUNd4mm74Kx/a5JqKu6QspEQZpAwH1Ez2SlbguOMWKHPTels2xbzA==", "dev": true, "requires": { - "yaml": "^2.4.1", - "yaml-types": "^0.3.0" + "yaml": "^2.8.1", + "yaml-types": "^0.4.0" } }, "tar": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", - "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.1.tgz", + "integrity": "sha512-nlGpxf+hv0v7GkWBK2V9spgactGOp0qvfWRxUMjqHyzrt3SgwE48DIv/FhqPHJYLHpgW1opq3nERbz5Anq7n1g==", "dev": true, "requires": { - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "minipass": "^5.0.0", - "minizlib": "^2.1.1", - "mkdirp": "^1.0.3", - "yallist": "^4.0.0" - }, - "dependencies": { - "fs-minipass": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", - "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", - "dev": true, - "requires": { - "minipass": "^3.0.0" - }, - "dependencies": { - "minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - } - } - }, - "minipass": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", - "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", - "dev": true - }, - "mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true - } + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.1.0", + "yallist": "^5.0.0" } }, "tcompare": { @@ -9038,20 +9594,74 @@ } }, "test-exclude": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", - "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.1.tgz", + "integrity": "sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==", "dev": true, "requires": { "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" + "glob": "^10.4.1", + "minimatch": "^9.0.4" + }, + "dependencies": { + "brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "requires": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + } + }, + "minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + } } }, - "text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==" + "tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "requires": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "dependencies": { + "fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "requires": {} + }, + "picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true + } + } }, "to-regex-range": { "version": "5.0.1", @@ -9069,46 +9679,62 @@ "dev": true }, "tshy": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/tshy/-/tshy-1.14.0.tgz", - "integrity": "sha512-YiUujgi4Jb+t2I48LwSRzHkBpniH9WjjktNozn+nlsGmVemKSjDNY7EwBRPvPCr5zAC/3ITAYWH9Z7kUinGSrw==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/tshy/-/tshy-3.0.3.tgz", + "integrity": "sha512-bUX6HQCvVdPyPLy2VZuKw95CtYD5aRSEgYEK7IPV9l9xN/z284kl5/hIwOfLY/mZOOdhrO34dFOOcL1VUMVyaw==", "dev": true, "requires": { - "chalk": "^5.3.0", - "chokidar": "^3.6.0", - "foreground-child": "^3.1.1", - "minimatch": "^9.0.4", + "chalk": "^5.6.2", + "chokidar": "^4.0.3", + "foreground-child": "^3.3.1", + "minimatch": "^10.0.3", "mkdirp": "^3.0.1", - "polite-json": "^4.0.1", - "resolve-import": "^1.4.5", - "rimraf": "^5.0.1", - "sync-content": "^1.0.2", - "typescript": "^5.4.5", - "walk-up-path": "^3.0.1" + "polite-json": "^5.0.0", + "resolve-import": "^2.0.0", + "rimraf": "^6.0.1", + "sync-content": "^2.0.1", + "typescript": "^5.9.3", + "walk-up-path": "^4.0.0" }, "dependencies": { - "brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "dev": true + }, + "chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", "dev": true, "requires": { - "balanced-match": "^1.0.0" + "readdirp": "^4.0.1" } }, - "chalk": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", - "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "minimatch": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.3.tgz", + "integrity": "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==", + "dev": true, + "requires": { + "@isaacs/brace-expansion": "^5.0.0" + } + }, + "readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", "dev": true }, - "minimatch": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", - "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", + "rimraf": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-6.0.1.tgz", + "integrity": "sha512-9dkvaxAsk/xNXSJzMgFqqMCuFgt2+KsOFek3TMLfo8NCPfWpBmqwyNn5Y+NX56QUYfCtsyhF3ayiboEoUmJk/A==", "dev": true, "requires": { - "brace-expansion": "^2.0.1" + "glob": "^11.0.0", + "package-json-from-dist": "^1.0.0" } } } @@ -9119,14 +9745,54 @@ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, "tuf-js": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tuf-js/-/tuf-js-2.2.1.tgz", - "integrity": "sha512-GwIJau9XaA8nLVbUXsN3IlFi7WmQ48gBUrl3FTkkL/XLu/POhBzfmX9hd33FNMX1qAsfl6ozO1iMmW9NC8YniA==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/tuf-js/-/tuf-js-4.0.0.tgz", + "integrity": "sha512-Lq7ieeGvXDXwpoSmOSgLWVdsGGV9J4a77oDTAPe/Ltrqnnm/ETaRlBAQTH5JatEh8KXuE6sddf9qAv1Q2282Hg==", "dev": true, "requires": { - "@tufjs/models": "2.0.1", - "debug": "^4.3.4", - "make-fetch-happen": "^13.0.1" + "@tufjs/models": "4.0.0", + "debug": "^4.4.1", + "make-fetch-happen": "^15.0.0" + }, + "dependencies": { + "@npmcli/agent": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-4.0.0.tgz", + "integrity": "sha512-kAQTcEN9E8ERLVg5AsGwLNoFb+oEG6engbqAU2P43gD4JEIkNGMHdVQ096FsOAAYpZPB0RSt0zgInKIAS1l5QA==", + "dev": true, + "requires": { + "agent-base": "^7.1.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.1", + "lru-cache": "^11.2.1", + "socks-proxy-agent": "^8.0.3" + } + }, + "lru-cache": { + "version": "11.2.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.2.tgz", + "integrity": "sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==", + "dev": true + }, + "make-fetch-happen": { + "version": "15.0.2", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-15.0.2.tgz", + "integrity": "sha512-sI1NY4lWlXBAfjmCtVWIIpBypbBdhHtcjnwnv+gtCnsaOffyFil3aidszGC8hgzJe+fT1qix05sWxmD/Bmf/oQ==", + "dev": true, + "requires": { + "@npmcli/agent": "^4.0.0", + "cacache": "^20.0.1", + "http-cache-semantics": "^4.1.1", + "minipass": "^7.0.2", + "minipass-fetch": "^4.0.0", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^1.0.0", + "proc-log": "^5.0.0", + "promise-retry": "^2.0.1", + "ssri": "^12.0.0" + } + } } }, "type-check": { @@ -9138,36 +9804,37 @@ } }, "type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==" + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "dev": true }, "typescript": { - "version": "5.4.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", - "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true }, "undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "version": "7.14.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.14.0.tgz", + "integrity": "sha512-QQiYxHuyZ9gQUIrmPo3IA+hUl4KYk8uSA7cHrcKd/l3p1OTpZcM0Tbp9x7FAtXdAYhlasd60ncPpgu6ihG6TOA==", "dev": true, "peer": true }, "unique-filename": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-3.0.0.tgz", - "integrity": "sha512-afXhuC55wkAmZ0P18QsVE6kp8JaxrEokN2HGIoIVv2ijHQd419H0+6EigAFcIzXeMIkcIkNBpB3L/DXB3cTS/g==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-4.0.0.tgz", + "integrity": "sha512-XSnEewXmQ+veP7xX2dS5Q4yZAvO40cBN2MWkJ7D/6sW4Dg6wYBNwM1Vrnz1FhH5AdeLIlUXRI9e28z1YZi71NQ==", "dev": true, "requires": { - "unique-slug": "^4.0.0" + "unique-slug": "^5.0.0" } }, "unique-slug": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-4.0.0.tgz", - "integrity": "sha512-WrcA6AyEfqDX5bWige/4NQfPZMtASNVxdmWR76WESYQVAACSgWcR6e9i0mofqqBxYFtL4oAxPIptY73/0YE1DQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-5.0.0.tgz", + "integrity": "sha512-9OdaqO5kwqR+1kVgHAhsp5vPNU0hnxRa26rBFNfNgM7M6pNtgzeBn3s/xbyCQL3dcjzOatcef6UUHpB/6MaETg==", "dev": true, "requires": { "imurmurhash": "^0.1.4" @@ -9194,9 +9861,9 @@ "dev": true }, "v8-to-istanbul": { - "version": "9.2.0", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.2.0.tgz", - "integrity": "sha512-/EH/sDgxU2eGxajKdwLCDmQ4FWq+kpi3uCmBGpw1xJtnAxEjlD8j8PEiGWpCIMIs3ciNAgH0d3TTJiUkYzyZjA==", + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", + "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", "dev": true, "requires": { "@jridgewell/trace-mapping": "^0.3.12", @@ -9205,9 +9872,9 @@ }, "dependencies": { "@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", "dev": true, "requires": { "@jridgewell/resolve-uri": "^3.1.0", @@ -9227,15 +9894,15 @@ } }, "validate-npm-package-name": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-5.0.1.tgz", - "integrity": "sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-6.0.2.tgz", + "integrity": "sha512-IUoow1YUtvoBBC06dXs8bR8B9vuA3aJfmQNKMoaPG/OFsPmoQvw8xh+6Ye25Gx9DQhoEom3Pcu9MKHerm/NpUQ==", "dev": true }, "walk-up-path": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/walk-up-path/-/walk-up-path-3.0.1.tgz", - "integrity": "sha512-9YlCL/ynK3CTlrSRrDxZvUauLzAswPCrsaCgilqFevUYpeEW0/3ScEjaa3kbW/T0ghhkEr7mv+fpjqn1Y1YuTA==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/walk-up-path/-/walk-up-path-4.0.0.tgz", + "integrity": "sha512-3hu+tD8YzSLGuFYtPRb48vdhKMi0KQV5sn+uWr8+7dMEq/2G/dtLrdDinkLjqq5TIbIBjYJ4Ax/n3YiaW7QM8A==", "dev": true }, "which": { @@ -9247,14 +9914,53 @@ } }, "widest-line": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-4.0.1.tgz", - "integrity": "sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-5.0.0.tgz", + "integrity": "sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA==", "dev": true, "requires": { - "string-width": "^5.0.1" + "string-width": "^7.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true + }, + "emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "dev": true + }, + "string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "requires": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + } + }, + "strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "dev": true, + "requires": { + "ansi-regex": "^6.0.1" + } + } } }, + "word-count": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/word-count/-/word-count-0.3.1.tgz", + "integrity": "sha512-tTDKyKX1yy8FZWUw64TOgnMeLeXPZBA+ZunaCCpFRrGFeLSwSdEgSsFI/5DIJkiUfM7CkCREFPkZd9U4eLXgqA==" + }, "word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", @@ -9322,15 +10028,10 @@ } } }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" - }, "ws": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", - "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", "dev": true, "requires": {} }, @@ -9341,21 +10042,21 @@ "dev": true }, "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", "dev": true }, "yaml": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.2.tgz", - "integrity": "sha512-B3VqDZ+JAg1nZpaEmWtTXUlBneoGx6CPM9b0TENK6aoSu5t73dItudwdgmi6tHlIZZId4dZ9skcAQ2UbcyAeVA==", + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz", + "integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==", "dev": true }, "yaml-types": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/yaml-types/-/yaml-types-0.3.0.tgz", - "integrity": "sha512-i9RxAO/LZBiE0NJUy9pbN5jFz5EasYDImzRkj8Y81kkInTi1laia3P3K/wlMKzOxFQutZip8TejvQP/DwgbU7A==", + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/yaml-types/-/yaml-types-0.4.0.tgz", + "integrity": "sha512-XfbA30NUg4/LWUiplMbiufUiwYhgB9jvBhTWel7XQqjV+GaB79c2tROu/8/Tu7jO0HvDvnKWtBk5ksWRrhQ/0g==", "dev": true, "requires": {} }, @@ -9410,10 +10111,10 @@ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==" }, - "yoga-wasm-web": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/yoga-wasm-web/-/yoga-wasm-web-0.3.3.tgz", - "integrity": "sha512-N+d4UJSJbt/R3wqY7Coqs5pcV0aUj2j9IaQ3rNj9bVCLld8tTGKRa2USARjnvZJWVx1NDmQev8EknoczaOQDOA==", + "yoga-layout": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/yoga-layout/-/yoga-layout-3.2.1.tgz", + "integrity": "sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ==", "dev": true } } diff --git a/package.json b/package.json index d19da806..bce87a58 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ }, "scripts": { "test": "tap", - "dev": "eslint src && node src/upd8.js" + "lint": "eslint src" }, "imports": { "#aggregate": "./src/aggregate.js", @@ -24,10 +24,11 @@ "#composite/things/art-tag": "./src/data/composite/things/art-tag/index.js", "#composite/things/artist": "./src/data/composite/things/artist/index.js", "#composite/things/artwork": "./src/data/composite/things/artwork/index.js", - "#composite/things/commentary-entry": "./src/data/composite/things/commentary-entry/index.js", + "#composite/things/content": "./src/data/composite/things/content/index.js", "#composite/things/contribution": "./src/data/composite/things/contribution/index.js", "#composite/things/flash": "./src/data/composite/things/flash/index.js", "#composite/things/flash-act": "./src/data/composite/things/flash-act/index.js", + "#composite/things/language": "./src/data/composite/things/language/index.js", "#composite/things/track": "./src/data/composite/things/track/index.js", "#composite/things/track-section": "./src/data/composite/things/track-section/index.js", "#content-dependencies": "./src/content/dependencies/index.js", @@ -45,7 +46,8 @@ "#replacer": "./src/replacer.js", "#reverse": "./src/reverse.js", "#search": "./src/search.js", - "#search-spec": "./src/common-util/search-spec.js", + "#search-shape": "./src/common-util/search-shape.js", + "#search-select": "./src/search-select.js", "#serialize": "./src/data/serialize.js", "#sort": "./src/common-util/sort.js", "#sugar": "./src/common-util/sugar.js", @@ -63,29 +65,26 @@ "node": ">= 22.13.0" }, "dependencies": { - "@js-temporal/polyfill": "^0.4.4", - "chroma-js": "^2.4.2", - "command-exists": "^1.2.9", - "compress-json": "^3.0.5", - "eslint": "^8.37.0", - "flexsearch": "^0.7.43", - "he": "^1.2.0", - "image-size": "^1.0.2", - "js-yaml": "^4.1.0", - "marked": "^10.0.0", - "msgpackr": "^1.10.2", - "rimraf": "^5.0.7", - "striptags": "^4.0.0-alpha.4", - "word-wrap": "^1.2.3" + "@eslint/js": "9.37.0", + "@js-temporal/polyfill": "0.4.4", + "chroma-js": "2.4.2", + "command-exists": "1.2.9", + "compress-json": "3.4.0", + "eslint": "9.37.0", + "flexsearch": "0.7.43", + "image-size": "2.0.2", + "js-yaml": "4.1.0", + "marked": "16.4.1", + "msgpackr": "1.11.1", + "rimraf": "5.0.7", + "striptags": "4.0.0-alpha.4", + "word-count": "0.3.1", + "word-wrap": "1.2.5" }, "license": "MIT", "devDependencies": { "chokidar": "^3.5.3", - "tap": "^19.0.2", + "tap": "^21.1.1", "tcompare": "^6.0.0" - }, - "tap": { - "coverage": false, - "coverage-report": false } } diff --git a/src/aggregate.js b/src/aggregate.js index cb806e89..3ff1846b 100644 --- a/src/aggregate.js +++ b/src/aggregate.js @@ -371,11 +371,12 @@ export function _withAggregate(mode, aggregateOpts, fn) { } export const unhelpfulTraceLines = [ - /sugar/, - /sort/, - /aggregate/, - /composite/, - /cacheable-object/, + /sugar\.js/, + /sort\.js/, + /aggregate\.js/, + /composite\.js/, + /cacheable-object\.js/, + /html\.js/, /node:/, /<anonymous>/, ]; @@ -604,7 +605,7 @@ export function showAggregate(topError, { headerPart += ` ${colors.dim(tracePart)}`; } - const head1 = level % 2 === 0 ? '\u21aa' : colors.dim('\u21aa'); + const head1 = '\u21aa'; const bar1 = ' '; const causePart = diff --git a/src/cli.js b/src/cli.js index bd4ec685..ec72a625 100644 --- a/src/cli.js +++ b/src/cli.js @@ -376,77 +376,79 @@ decorateTime.displayTime = function () { } }; -export function progressPromiseAll(msgOrMsgFn, array) { +const progressUpdateInterval = 1000 / 60; + +function progressShow(message, total) { + let start = Date.now(), last = 0, done = 0; + + const progress = () => { + const messagePart = + (typeof message === 'function' + ? message() + : message); + + const percent = + Math.round((done / total) * 1000) / 10 + '%'; + + const percentPart = + percent.padEnd('99.9%'.length, ' '); + + return `${messagePart} [${percentPart}]`; + }; + + process.stdout.write(`\r` + progress()); + + return () => { + done++; + + if (done === total) { + process.stdout.write( + `\r\x1b[2m` + progress() + + `\x1b[0;32m Done! ` + + `\x1b[0;2m(${formatDuration(Date.now() - start)}) ` + + `\x1b[0m\n` + ); + } else if (Date.now() - last >= progressUpdateInterval) { + process.stdout.write('\r' + progress()); + last = Date.now(); + } + }; +} + +export function progressPromiseAll(message, array) { if (!array.length) { return Promise.resolve([]); } - const msgFn = - typeof msgOrMsgFn === 'function' ? msgOrMsgFn : () => msgOrMsgFn; - - let done = 0, - total = array.length; - process.stdout.write(`\r${msgFn()} [0/${total}]`); - const start = Date.now(); - return Promise.all( - array.map((promise) => - Promise.resolve(promise).then((val) => { - done++; - // const pc = `${done}/${total}`; - const pc = (Math.round((done / total) * 1000) / 10 + '%').padEnd( - '99.9%'.length, - ' ' - ); - if (done === total) { - const time = Date.now() - start; - process.stdout.write( - `\r\x1b[2m${msgFn()} [${pc}] \x1b[0;32mDone! \x1b[0;2m(${time} ms) \x1b[0m\n` - ); - } else { - process.stdout.write(`\r${msgFn()} [${pc}] `); - } - return val; - }) - ) - ); + const show = progressShow(message, array.length); + + const next = value => { + show(); + + return value; + }; + + const promises = + array.map(promise => Promise.resolve(promise).then(next)); + + return Promise.all(promises); } -export function progressCallAll(msgOrMsgFn, array) { +export function progressCallAll(message, array) { if (!array.length) { return []; } - const msgFn = - typeof msgOrMsgFn === 'function' ? msgOrMsgFn : () => msgOrMsgFn; + const show = progressShow(message, array.length); - const updateInterval = 1000 / 60; - - let done = 0, - total = array.length; - process.stdout.write(`\r${msgFn()} [0/${total}]`); - const start = Date.now(); - const vals = []; - let lastTime = 0; + const values = []; for (const fn of array) { - const val = fn(); - done++; - - if (done === total) { - const pc = '100%'.padEnd('99.9%'.length, ' '); - const time = Date.now() - start; - process.stdout.write( - `\r\x1b[2m${msgFn()} [${pc}] \x1b[0;32mDone! \x1b[0;2m(${time} ms) \x1b[0m\n` - ); - } else if (Date.now() - lastTime >= updateInterval) { - const pc = (Math.round((done / total) * 1000) / 10 + '%').padEnd('99.9%'.length, ' '); - process.stdout.write(`\r${msgFn()} [${pc}] `); - lastTime = Date.now(); - } - vals.push(val); + values.push(fn()); + show(); } - return vals; + return values; } export function fileIssue({ @@ -459,6 +461,24 @@ export function fileIssue({ console.error(colors.red(`- https://github.com/hsmusic/hsmusic-wiki/issues/`)); } +// Quick'n dirty function to present a duration nicely for command-line use. +export function formatDuration(timeDelta) { + const seconds = timeDelta / 1000; + + if (seconds > 90) { + const modSeconds = Math.floor(seconds % 60); + const minutes = Math.floor(seconds - seconds % 60) / 60; + return `${minutes}m${modSeconds}s`; + } + + if (seconds < 0.1) { + return 'instant'; + } + + const precision = (seconds > 1 ? 3 : 2); + return `${seconds.toPrecision(precision)}s`; +} + export async function logicalCWD() { if (process.env.PWD) { return process.env.PWD; @@ -469,7 +489,7 @@ export async function logicalCWD() { try { await stat('/bin/sh'); - } catch (error) { + } catch { // Not logical, so sad. return process.cwd(); } diff --git a/src/common-util/search-shape.js b/src/common-util/search-shape.js new file mode 100644 index 00000000..e0819ed6 --- /dev/null +++ b/src/common-util/search-shape.js @@ -0,0 +1,58 @@ +// Index structures shared by client and server, and relevant interfaces. +// First and foremost, this is complemented by src/search-select.js, which +// actually fills the search indexes up with stuff. During build this all +// gets consumed by src/search.js to make an index, fill it with stuff +// (as described by search-select.js), and export it to disk; then on +// the client that export is consumed by src/static/js/search-worker.js, +// which builds an index in the same shape and imports the data for query. + +const baselineStore = [ + 'primaryName', + 'disambiguator', + 'artwork', + 'color', +]; + +const genericStore = baselineStore; + +const searchShape = { + generic: { + index: [ + 'primaryName', + 'parentName', + 'artTags', + 'additionalNames', + 'contributors', + 'groups', + ].map(field => ({field, tokenize: 'forward'})), + + store: genericStore, + }, + + verbatim: { + index: [ + 'primaryName', + 'parentName', + 'artTags', + 'additionalNames', + 'contributors', + 'groups', + ], + + store: genericStore, + }, +}; + +export default searchShape; + +export function makeSearchIndex(descriptor, {FlexSearch}) { + return new FlexSearch.Document({ + id: 'reference', + index: descriptor.index, + store: descriptor.store, + + // Disable scoring, always return results according to provided order + // (specified above in `genericQuery`, etc). + resolution: 1, + }); +} diff --git a/src/common-util/search-spec.js b/src/common-util/search-spec.js deleted file mode 100644 index af5ec201..00000000 --- a/src/common-util/search-spec.js +++ /dev/null @@ -1,280 +0,0 @@ -// Index structures shared by client and server, and relevant interfaces. - -function getArtworkPath(thing) { - switch (thing.constructor[Symbol.for('Thing.referenceType')]) { - case 'album': { - return [ - 'media.albumCover', - thing.directory, - thing.coverArtFileExtension, - ]; - } - - case 'flash': { - return [ - 'media.flashArt', - thing.directory, - thing.coverArtFileExtension, - ]; - } - - case 'track': { - if (thing.hasUniqueCoverArt) { - return [ - 'media.trackCover', - thing.album.directory, - thing.directory, - thing.coverArtFileExtension, - ]; - } else if (thing.album.hasCoverArt) { - return [ - 'media.albumCover', - thing.album.directory, - thing.album.coverArtFileExtension, - ]; - } else { - return null; - } - } - - default: - return null; - } -} - -function prepareArtwork(thing, { - checkIfImagePathHasCachedThumbnails, - getThumbnailEqualOrSmaller, - urls, -}) { - const hasWarnings = - thing.artTags?.some(artTag => artTag.isContentWarning); - - const artworkPath = - getArtworkPath(thing); - - if (!artworkPath) { - return undefined; - } - - const mediaSrc = - urls - .from('media.root') - .to(...artworkPath); - - if (!checkIfImagePathHasCachedThumbnails(mediaSrc)) { - return undefined; - } - - const selectedSize = - getThumbnailEqualOrSmaller( - (hasWarnings ? 'mini' : 'adorb'), - mediaSrc); - - const mediaSrcJpeg = - mediaSrc.replace(/\.(png|jpg)$/, `.${selectedSize}.jpg`); - - const displaySrc = - urls - .from('thumb.root') - .to('thumb.path', mediaSrcJpeg); - - const serializeSrc = - displaySrc.replace(thing.directory, '<>'); - - return serializeSrc; -} - -function baselineProcess(thing, opts) { - const fields = {}; - - fields.primaryName = - thing.name; - - fields.artwork = - prepareArtwork(thing, opts); - - fields.color = - thing.color; - - return fields; -} - -const baselineStore = [ - 'primaryName', - 'artwork', - 'color', -]; - -function genericQuery(wikiData) { - return [ - wikiData.albumData, - - wikiData.artTagData, - - wikiData.artistData - .filter(artist => !artist.isAlias), - - wikiData.flashData, - - wikiData.groupData, - - wikiData.trackData - // Exclude rereleases - there's no reasonable way to differentiate - // them from the main release as part of this query. - .filter(track => !track.mainReleaseTrack), - ].flat(); -} - -function genericProcess(thing, opts) { - const fields = baselineProcess(thing, opts); - - const kind = - thing.constructor[Symbol.for('Thing.referenceType')]; - - fields.parentName = - (kind === 'track' - ? thing.album.name - : kind === 'group' - ? thing.category.name - : kind === 'flash' - ? thing.act.name - : null); - - fields.artTags = - (thing.constructor.hasPropertyDescriptor('artTags') - ? thing.artTags.map(artTag => artTag.nameShort) - : []); - - fields.additionalNames = - (thing.constructor.hasPropertyDescriptor('additionalNames') - ? thing.additionalNames.map(entry => entry.name) - : thing.constructor.hasPropertyDescriptor('aliasNames') - ? thing.aliasNames - : []); - - const contribKeys = [ - 'artistContribs', - 'contributorContribs', - ]; - - const contributions = - contribKeys - .filter(key => Object.hasOwn(thing, key)) - .flatMap(key => thing[key]); - - fields.contributors = - contributions - .flatMap(({artist}) => [ - artist.name, - ...artist.aliasNames, - ]); - - const groups = - (Object.hasOwn(thing, 'groups') - ? thing.groups - : Object.hasOwn(thing, 'album') - ? thing.album.groups - : []); - - const mainContributorNames = - contributions - .map(({artist}) => artist.name); - - fields.groups = - groups - .filter(group => !mainContributorNames.includes(group.name)) - .map(group => group.name); - - return fields; -} - -const genericStore = baselineStore; - -export const searchSpec = { - generic: { - query: genericQuery, - process: genericProcess, - - index: [ - 'primaryName', - 'parentName', - 'artTags', - 'additionalNames', - 'contributors', - 'groups', - ].map(field => ({field, tokenize: 'forward'})), - - store: genericStore, - }, - - verbatim: { - query: genericQuery, - process: genericProcess, - - index: [ - 'primaryName', - 'parentName', - 'artTags', - 'additionalNames', - 'contributors', - 'groups', - ], - - store: genericStore, - }, -}; - -export function makeSearchIndex(descriptor, {FlexSearch}) { - return new FlexSearch.Document({ - id: 'reference', - index: descriptor.index, - store: descriptor.store, - }); -} - -// TODO: This function basically mirrors bind-utilities.js, which isn't -// exactly robust, but... binding might need some more thought across the -// codebase in *general.* -function bindSearchUtilities({ - checkIfImagePathHasCachedThumbnails, - getThumbnailEqualOrSmaller, - thumbsCache, - urls, -}) { - const bound = { - urls, - }; - - bound.checkIfImagePathHasCachedThumbnails = - (imagePath) => - checkIfImagePathHasCachedThumbnails(imagePath, thumbsCache); - - bound.getThumbnailEqualOrSmaller = - (preferred, imagePath) => - getThumbnailEqualOrSmaller(preferred, imagePath, thumbsCache); - - return bound; -} - -export function populateSearchIndex(index, descriptor, opts) { - const {wikiData} = opts; - const bound = bindSearchUtilities(opts); - - const collection = descriptor.query(wikiData); - - for (const thing of collection) { - const reference = thing.constructor.getReference(thing); - - let processed; - try { - processed = descriptor.process(thing, bound); - } catch (caughtError) { - throw new Error( - `Failed to process searchable thing ${reference}`, - {cause: caughtError}); - } - - index.add({reference, ...processed}); - } -} diff --git a/src/common-util/sort.js b/src/common-util/sort.js index d93d94c1..bbe4e551 100644 --- a/src/common-util/sort.js +++ b/src/common-util/sort.js @@ -370,11 +370,12 @@ export function sortAlbumsTracksChronologically(data, { getDate, } = {}) { // Sort albums before tracks... - sortByConditions(data, [(t) => t.album === undefined]); + sortByConditions(data, [t => t.isAlbum]); - // Group tracks by album... - sortByDirectory(data, { - getDirectory: (t) => (t.album ? t.album.directory : t.directory), + // Put albums alphabetically, and group with them... + sortAlphabetically(data, { + getDirectory: t => t.isTrack ? t.album.directory : t.directory, + getName: t => t.isTrack ? t.album.name : t.name, }); // Sort tracks by position in album... diff --git a/src/common-util/sugar.js b/src/common-util/sugar.js index 66e160aa..354cf5cc 100644 --- a/src/common-util/sugar.js +++ b/src/common-util/sugar.js @@ -70,6 +70,16 @@ export function pick(array) { return array[Math.floor(Math.random() * array.length)]; } +// Gets the only item in a single-item array (strictly, length === 1). +// If the array has more than one item, or is empty, this is null. +export function onlyItem(array) { + if (array.length === 1) { + return array[0]; + } else { + return null; + } +} + // Gets the item at an index relative to another index. export function atOffset(array, index, offset, { wrap = false, @@ -116,10 +126,14 @@ export function findIndexOrEnd(array, fn) { // returns null (or values in the array are nullish), they'll just be skipped in // the sum. export function accumulateSum(array, fn = x => x) { + if (!Array.isArray(array)) { + return accumulateSum(Array.from(array, fn)); + } + return array.reduce( (accumulator, value, index, array) => accumulator + - fn(value, index, array) ?? 0, + (fn(value, index, array) ?? 0), 0); } @@ -221,6 +235,9 @@ export const compareArrays = (arr1, arr2, {checkOrder = true} = {}) => ? arr1.every((x, i) => arr2[i] === x) : arr1.every((x) => arr2.includes(x))); +export const exhaust = (generatorFunction) => + Array.from(generatorFunction()); + export function compareObjects(obj1, obj2, { checkOrder = false, checkSymbols = true, @@ -251,11 +268,20 @@ export function compareObjects(obj1, obj2, { // Stolen from jq! Which pro8a8ly stole the concept from other places. Nice. export const withEntries = (obj, fn) => { - const result = fn(Object.entries(obj)); - if (result instanceof Promise) { - return result.then(entries => Object.fromEntries(entries)); + if (obj instanceof Map) { + const result = fn(Array.from(obj.entries())); + if (result instanceof Promise) { + return result.then(entries => new Map(entries)); + } else { + return new Map(result); + } } else { - return Object.fromEntries(result); + const result = fn(Object.entries(obj)); + if (result instanceof Promise) { + return result.then(entries => Object.fromEntries(entries)); + } else { + return Object.fromEntries(result); + } } } @@ -299,34 +325,74 @@ export function filterProperties(object, properties, { return filteredObject; } -export function queue(array, max = 50) { - if (max === 0) { - return array.map((fn) => fn()); +export function queue(functionList, queueSize = 50) { + if (queueSize === 0) { + return functionList.map(fn => fn()); } - const begin = []; - let current = 0; - const ret = array.map( - (fn) => - new Promise((resolve, reject) => { - begin.push(() => { - current++; - Promise.resolve(fn()).then((value) => { - current--; - if (current < max && begin.length) { - begin.shift()(); - } - resolve(value); - }, reject); - }); - }) - ); + const promiseList = []; + const resolveList = []; + const rejectList = []; - for (let i = 0; i < max && begin.length; i++) { - begin.shift()(); + for (let i = 0; i < functionList.length; i++) { + const promiseWithResolvers = Promise.withResolvers(); + promiseList.push(promiseWithResolvers.promise); + resolveList.push(promiseWithResolvers.resolve); + rejectList.push(promiseWithResolvers.reject); } - return ret; + let cursor = 0; + let running = 0; + + const next = async () => { + if (running >= queueSize) { + return; + } + + if (cursor === functionList.length) { + return; + } + + const thisFunction = functionList[cursor]; + const thisResolve = resolveList[cursor]; + const thisReject = rejectList[cursor]; + + delete functionList[cursor]; + delete resolveList[cursor]; + delete rejectList[cursor]; + + cursor++; + running++; + + try { + thisResolve(await thisFunction()); + } catch (error) { + thisReject(error); + } finally { + running--; + + // If the cursor is at 1, this is the first promise that resolved, + // so we're now done the "kick start", and can start the remaining + // promises (up to queueSize). + if (cursor === 1) { + // Since only one promise is used for the "kick start", and that one + // has just resolved, we know there's none running at all right now, + // and can start as many as specified in the queueSize right away. + for (let i = 0; i < queueSize; i++) { + next(); + } + } else { + next(); + } + } + }; + + // Only start a single promise, as a "kick start", so that it resolves as + // early as possible (it will resolve before we use CPU to start the rest + // of the promises, up to queueSize). + next(); + + return promiseList; } export function delay(ms) { @@ -357,15 +423,23 @@ export function splitKeys(key) { // Follows a key path like 'foo.bar.baz' to get an item nested deeply inside // an object. If a value partway through the chain is an array, the values -// down the rest of the chain are gotten for each item in the array. +// down the rest of the chain are gotten for each item in the array. If a value +// partway through the chain is missing the next key, the chain stops and is +// undefined (or null) at that point. // // obj: {x: [{y: ['a']}, {y: ['b', 'c']}]} // key: 'x.y' // -> [['a'], ['b', 'c']] // +// obj: {x: [{y: ['a']}, {y: ['b', 'c']}, {z: ['d', 'e']}]} +// key: 'x.z' +// -> [undefined, undefined, ['d', 'e']] +// export function getNestedProp(obj, key) { const recursive = (o, k) => - (k.length === 1 + (o === undefined || o === null + ? o + : k.length === 1 ? o[k[0]] : Array.isArray(o[k[0]]) ? o[k[0]].map(v => recursive(v, k.slice(1))) diff --git a/src/common-util/wiki-data.js b/src/common-util/wiki-data.js index a4c6b3bd..6089b8fc 100644 --- a/src/common-util/wiki-data.js +++ b/src/common-util/wiki-data.js @@ -11,7 +11,7 @@ export {filterMultipleArrays} from './sugar.js'; // Generic value operations -export function getKebabCase(name) { +export function getCaseSensitiveKebabCase(name) { return name // Spaces to dashes @@ -34,6 +34,9 @@ export function getKebabCase(name) { // General punctuation which always separates surrounding words .replace(/[/@#$%*()_=,[\]{}|\\;:<>?`~]/g, '-') + // More punctuation which always separates surrounding words + .replace(/[\u{2013}-\u{2014}]/u, '-') // En Dash, Em Dash + // Accented characters .replace(/[áâäàå]/gi, 'a') .replace(/[çč]/gi, 'c') @@ -50,17 +53,17 @@ export function getKebabCase(name) { // Trim dashes on boundaries .replace(/^-+|-+$/g, '') +} - // Always lowercase - .toLowerCase(); +export function getKebabCase(name) { + return getCaseSensitiveKebabCase(name).toLowerCase(); } // Specific data utilities -// Matches heading details from commentary data in roughly the formats: +// Matches heading details from commentary data in roughly the format: // -// <i>artistReference:</i> (annotation, date) -// <i>artistReference|artistDisplayText:</i> (annotation, date) +// <i>artistText:</i> (annotation, date) // // where capturing group "annotation" can be any text at all, except that the // last entry (past a comma or the only content within parentheses), if parsed @@ -83,8 +86,9 @@ export function getKebabCase(name) { // parentheses can be part of the actual annotation content. // // Capturing group "artistReference" is all the characters between <i> and </i> -// (apart from the pipe and "artistDisplayText" text, if present), and is either -// the name of an artist or an "artist:directory"-style reference. +// (apart from the pipe and the "artistText" group, if present), and is either +// the name of one or more artist or "artist:directory"-style references, +// joined by commas, if multiple. // // This regular expression *doesn't* match bodies, which will need to be parsed // out of the original string based on the indices matched using this. @@ -94,7 +98,7 @@ const dateRegex = groupName => String.raw`(?<${groupName}>[a-zA-Z]+ [0-9]{1,2}, [0-9]{4,4}|[0-9]{1,2} [^,]*[0-9]{4,4}|[0-9]{1,4}[-/][0-9]{1,4}[-/][0-9]{1,4})`; const commentaryRegexRaw = - String.raw`^<i>(?<artistReferences>.+?)(?:\|(?<artistDisplayText>.+))?:<\/i>(?: \((?<annotation>(?:.*?(?=,|\)[^)]*$))*?)(?:,? ?(?:(?<dateKind>sometime|throughout|around) )?${dateRegex('date')}(?: ?- ?${dateRegex('secondDate')})?(?: (?<accessKind>captured|accessed) ${dateRegex('accessDate')})?)?\))?`; + String.raw`^<i>(?<artistText>.+?):<\/i>(?: \((?<annotation>(?:.*?(?=,|\)[^)]*$))*?)(?:,? ?(?:(?<dateKind>sometime|throughout|around) )?${dateRegex('date')}(?: ?- ?${dateRegex('secondDate')})?(?: (?<accessKind>captured|accessed) ${dateRegex('accessDate')})?)?\))?`; export const commentaryRegexCaseInsensitive = new RegExp(commentaryRegexRaw, 'gmi'); export const commentaryRegexCaseSensitive = @@ -102,6 +106,8 @@ export const commentaryRegexCaseSensitive = export const commentaryRegexCaseSensitiveOneShot = new RegExp(commentaryRegexRaw); +export const languageOptionRegex = /{(?<name>[A-Z0-9_]+)}/g; + // The #validators function isOldStyleLyrics() describes // what this regular expression detects against. export const multipleLyricsDetectionRegex = @@ -113,10 +119,16 @@ export function matchContentEntries(sourceText) { let previousMatchEntry = null; let previousEndIndex = null; + const trimBody = body => + body + .replace(/^\n*/, '') + .replace(/\n*$/, ''); + for (const {0: matchText, index: startIndex, groups: matchEntry} of sourceText.matchAll(commentaryRegexCaseSensitive)) { if (previousMatchEntry) { - previousMatchEntry.body = sourceText.slice(previousEndIndex, startIndex); + previousMatchEntry.body = + trimBody(sourceText.slice(previousEndIndex, startIndex)); } matchEntries.push(matchEntry); @@ -126,7 +138,8 @@ export function matchContentEntries(sourceText) { } if (previousMatchEntry) { - previousMatchEntry.body = sourceText.slice(previousEndIndex); + previousMatchEntry.body = + trimBody(sourceText.slice(previousEndIndex)); } return matchEntries; @@ -522,3 +535,52 @@ export function combineWikiDataArrays(arrays) { return combined; } } + +// Markdown stuff + +export function* matchMarkdownLinks(markdownSource, {marked}) { + const plausibleLinkRegexp = /\[(?=.*?\))/g; + + // Pedantic rules use more particular parentheses detection in link + // destinations - they allow one level of balanced parentheses, and + // otherwise, parentheses must be escaped. This allows for entire links + // to be wrapped in parentheses, e.g below: + // + // This is so cool. ([You know??](https://example.com)) + // + const definiteLinkRegexp = marked.Lexer.rules.inline.pedantic.link; + + let plausibleMatch = null; + while (plausibleMatch = plausibleLinkRegexp.exec(markdownSource)) { + const definiteMatch = + definiteLinkRegexp.exec(markdownSource.slice(plausibleMatch.index)); + + if (!definiteMatch) { + continue; + } + + const [{length}, label, href] = definiteMatch; + const index = plausibleMatch.index + definiteMatch.index; + + yield {label, href, index, length}; + } +} + +export function* matchInlineLinks(source) { + const plausibleLinkRegexp = /\b[a-z]*:\/\/[^ ]*?(?=(?:[,.!?]*)(?:\s|$))/gm; + + let plausibleMatch = null; + while (plausibleMatch = plausibleLinkRegexp.exec(source)) { + const [href] = plausibleMatch; + const {index} = plausibleMatch; + const [{length}] = plausibleMatch; + + try { + new URL(href); + } catch { + continue; + } + + yield {href, length, index}; + } +} diff --git a/src/content-function.js b/src/content-function.js index 44f8b842..04f2ce90 100644 --- a/src/content-function.js +++ b/src/content-function.js @@ -2,8 +2,8 @@ import {inspect as nodeInspect} from 'node:util'; import {decorateError} from '#aggregate'; import {colors, decorateTime, ENABLE_COLOR} from '#cli'; -import {Template} from '#html'; -import {annotateFunction, empty, setIntersection} from '#sugar'; +import {Tag, Template} from '#html'; +import {empty} from '#sugar'; function inspect(value, opts = {}) { return nodeInspect(value, {colors: ENABLE_COLOR, ...opts}); @@ -13,167 +13,117 @@ const DECORATE_TIME = process.env.HSMUSIC_DEBUG_CONTENT_PERF === '1'; export class ContentFunctionSpecError extends Error {} -export default function contentFunction({ - contentDependencies = [], - extraDependencies = [], - - slots, - sprawl, - query, - relations, - data, - generate, -}) { - const expectedContentDependencyKeys = new Set(contentDependencies); - const expectedExtraDependencyKeys = new Set(extraDependencies); - - // Initial checks. These only need to be run once per description of a - // content function, and don't depend on any mutable context (e.g. which - // dependencies have been fulfilled so far). - - const overlappingContentExtraDependencyKeys = - setIntersection(expectedContentDependencyKeys, expectedExtraDependencyKeys); - - if (!empty(overlappingContentExtraDependencyKeys)) { - throw new ContentFunctionSpecError(`Overlap in content and extra dependency keys: ${[...overlappingContentExtraDependencyKeys].join(', ')}`); +function optionalDecorateTime(prefix, dependency, fn) { + if (DECORATE_TIME) { + return decorateTime(`${prefix}/${dependency}`, fn); + } else { + return fn; } +} - if (!generate) { +export default function contentFunction(spec) { + if (!spec.generate) { throw new ContentFunctionSpecError(`Expected generate function`); } - if (sprawl && !expectedExtraDependencyKeys.has('wikiData')) { - throw new ContentFunctionSpecError(`Content functions which sprawl must specify wikiData in extraDependencies`); + if (spec.slots) { + Template.validateSlotsDescription(spec.slots); } - if (slots && !expectedExtraDependencyKeys.has('html')) { - throw new ContentFunctionSpecError(`Content functions with slots must specify html in extraDependencies`); - } + return expectExtraDependencies(spec, null); +} - if (slots) { - Template.validateSlotsDescription(slots); +contentFunction.identifyingSymbol = Symbol(`Is a content function?`); + +export function expectExtraDependencies(spec, boundExtraDependencies) { + const generate = + (boundExtraDependencies + ? prepareWorkingGenerateFunction(spec, boundExtraDependencies) + : () => { + throw new Error(`Not bound with extraDependencies yet`); + }); + + generate[contentFunction.identifyingSymbol] = true; + + const dependency = spec.generate.name; + for (const key of ['sprawl', 'query', 'relations', 'data']) { + if (spec[key]) { + generate[key] = optionalDecorateTime(key, dependency, spec[key]); + } } - // Pass all the details to expectDependencies, which will recursively build - // up a set of fulfilled dependencies and make functions like `relations` - // and `generate` callable only with sufficient fulfilled dependencies. - - return expectDependencies({ - slots, - sprawl, - query, - relations, - data, - generate, - - expectedContentDependencyKeys, - expectedExtraDependencyKeys, - missingContentDependencyKeys: new Set(expectedContentDependencyKeys), - missingExtraDependencyKeys: new Set(expectedExtraDependencyKeys), - invalidatingDependencyKeys: new Set(), - fulfilledDependencyKeys: new Set(), - fulfilledDependencies: {}, - }); + generate.bindExtraDependencies = (extraDependencies) => + expectExtraDependencies(spec, extraDependencies); + + return generate; } -contentFunction.identifyingSymbol = Symbol(`Is a content function?`); +function prepareWorkingGenerateFunction(spec, boundExtraDependencies) { + const dependency = spec.generate.name; -export function expectDependencies({ - slots, - sprawl, - query, - relations, - data, - generate, - - expectedContentDependencyKeys, - expectedExtraDependencyKeys, - missingContentDependencyKeys, - missingExtraDependencyKeys, - invalidatingDependencyKeys, - fulfilledDependencyKeys, - fulfilledDependencies, -}) { - const hasSprawlFunction = !!sprawl; - const hasQueryFunction = !!query; - const hasRelationsFunction = !!relations; - const hasDataFunction = !!data; - const hasSlotsDescription = !!slots; - - const isInvalidated = !empty(invalidatingDependencyKeys); - const isMissingContentDependencies = !empty(missingContentDependencyKeys); - const isMissingExtraDependencies = !empty(missingExtraDependencyKeys); - - let wrappedGenerate; - - const optionalDecorateTime = (prefix, fn) => - (DECORATE_TIME - ? decorateTime(`${prefix}/${generate.name}`, fn) - : fn); - - if (isInvalidated) { - wrappedGenerate = function() { - throw new Error(`Generate invalidated because unfulfilled dependencies provided: ${[...invalidatingDependencyKeys].join(', ')}`); - }; + let generate = ([arg1, arg2], ...extraArgs) => { + if (spec.data && !arg1) { + throw new Error(`Expected data`); + } - annotateFunction(wrappedGenerate, {name: generate, trait: 'invalidated'}); - wrappedGenerate.fulfilled = false; - } else if (isMissingContentDependencies || isMissingExtraDependencies) { - wrappedGenerate = function() { - throw new Error(`Dependencies still needed: ${[...missingContentDependencyKeys, ...missingExtraDependencyKeys].join(', ')}`); - }; + if (spec.data && spec.relations && !arg2) { + throw new Error(`Expected relations`); + } - annotateFunction(wrappedGenerate, {name: generate, trait: 'unfulfilled'}); - wrappedGenerate.fulfilled = false; - } else { - let callUnderlyingGenerate = ([arg1, arg2], ...extraArgs) => { - if (hasDataFunction && !arg1) { - throw new Error(`Expected data`); - } + if (spec.relations && !arg1) { + throw new Error(`Expected relations`); + } - if (hasDataFunction && hasRelationsFunction && !arg2) { - throw new Error(`Expected relations`); + try { + if (spec.data && spec.relations) { + return spec.generate(arg1, arg2, ...extraArgs, boundExtraDependencies); + } else if (spec.data || spec.relations) { + return spec.generate(arg1, ...extraArgs, boundExtraDependencies); + } else { + return spec.generate(...extraArgs, boundExtraDependencies); } + } catch (caughtError) { + const error = new Error( + `Error generating content for ${dependency}`, + {cause: caughtError}); - if (hasRelationsFunction && !arg1) { - throw new Error(`Expected relations`); - } + error[Symbol.for(`hsmusic.aggregate.alwaysTrace`)] = true; + error[Symbol.for(`hsmusic.aggregate.traceFrom`)] = caughtError; + + error[Symbol.for(`hsmusic.aggregate.unhelpfulTraceLines`)] = [ + /content-function\.js/, + /util\/html\.js/, + ]; + + error[Symbol.for(`hsmusic.aggregate.helpfulTraceLines`)] = [ + /content\/dependencies\/(.*\.js:.*(?=\)))/, + ]; + + throw error; + } + }; - try { - if (hasDataFunction && hasRelationsFunction) { - return generate(arg1, arg2, ...extraArgs, fulfilledDependencies); - } else if (hasDataFunction || hasRelationsFunction) { - return generate(arg1, ...extraArgs, fulfilledDependencies); - } else { - return generate(...extraArgs, fulfilledDependencies); - } - } catch (caughtError) { - const error = new Error( - `Error generating content for ${generate.name}`, - {cause: caughtError}); - - error[Symbol.for(`hsmusic.aggregate.alwaysTrace`)] = true; - error[Symbol.for(`hsmusic.aggregate.traceFrom`)] = caughtError; - - error[Symbol.for(`hsmusic.aggregate.unhelpfulTraceLines`)] = [ - /content-function\.js/, - /util\/html\.js/, - ]; - - error[Symbol.for(`hsmusic.aggregate.helpfulTraceLines`)] = [ - /content\/dependencies\/(.*\.js:.*(?=\)))/, - ]; - - throw error; + generate = (baseGenerate => (...args) => { + const result = baseGenerate(...args); + + if (result instanceof Template || result instanceof Tag) { + if (Object.hasOwn(result, Symbol.for('hsmusic.content.via'))) { + result[Symbol.for('hsmusic.contentFunction.via')].push(dependency); + } else { + result[Symbol.for('hsmusic.contentFunction.via')] = [dependency]; } - }; + } + + return result; + })(generate); - callUnderlyingGenerate = - optionalDecorateTime(`generate`, callUnderlyingGenerate); + generate = optionalDecorateTime(`generate`, dependency, generate); - if (hasSlotsDescription) { - const stationery = fulfilledDependencies.html.stationery({ - annotation: generate.name, + if (spec.slots) { + let stationery = null; + return (...args) => { + stationery ??= boundExtraDependencies.html.stationery({ + annotation: dependency, // These extra slots are for the data and relations (positional) args. // No hacks to store them temporarily or otherwise "invisibly" alter @@ -182,170 +132,23 @@ export function expectDependencies({ slots: { _cfArg1: {validate: v => v.isObject}, _cfArg2: {validate: v => v.isObject}, - ...slots, + ...spec.slots, }, content(slots) { const args = [slots._cfArg1, slots._cfArg2]; - return callUnderlyingGenerate(args, slots); + return generate(args, slots); }, }); - wrappedGenerate = function(...args) { - return stationery.template().slots({ - _cfArg1: args[0] ?? null, - _cfArg2: args[1] ?? null, - }); - }; - } else { - wrappedGenerate = function(...args) { - return callUnderlyingGenerate(args); - }; - } - - wrappedGenerate.fulfill = function() { - throw new Error(`All dependencies already fulfilled (${generate.name})`); - }; - - annotateFunction(wrappedGenerate, {name: generate, trait: 'fulfilled'}); - wrappedGenerate.fulfilled = true; - } - - wrappedGenerate[contentFunction.identifyingSymbol] = true; - - if (hasSprawlFunction) { - wrappedGenerate.sprawl = optionalDecorateTime(`sprawl`, sprawl); - } - - if (hasQueryFunction) { - wrappedGenerate.query = optionalDecorateTime(`query`, query); - } - - if (hasRelationsFunction) { - wrappedGenerate.relations = optionalDecorateTime(`relations`, relations); - } - - if (hasDataFunction) { - wrappedGenerate.data = optionalDecorateTime(`data`, data); - } - - wrappedGenerate.fulfill ??= function fulfill(dependencies) { - // To avoid unneeded destructuring, `fullfillDependencies` is a mutating - // function. But `fulfill` itself isn't meant to mutate! We create a copy - // of these variables, so their original values are kept for additional - // calls to this same `fulfill`. - const newlyMissingContentDependencyKeys = new Set(missingContentDependencyKeys); - const newlyMissingExtraDependencyKeys = new Set(missingExtraDependencyKeys); - const newlyInvalidatingDependencyKeys = new Set(invalidatingDependencyKeys); - const newlyFulfilledDependencyKeys = new Set(fulfilledDependencyKeys); - const newlyFulfilledDependencies = {...fulfilledDependencies}; - - try { - fulfillDependencies(dependencies, { - missingContentDependencyKeys: newlyMissingContentDependencyKeys, - missingExtraDependencyKeys: newlyMissingExtraDependencyKeys, - invalidatingDependencyKeys: newlyInvalidatingDependencyKeys, - fulfilledDependencyKeys: newlyFulfilledDependencyKeys, - fulfilledDependencies: newlyFulfilledDependencies, + return stationery.template().slots({ + _cfArg1: args[0] ?? null, + _cfArg2: args[1] ?? null, }); - } catch (error) { - error.message += ` (${generate.name})`; - throw error; - } - - return expectDependencies({ - slots, - sprawl, - query, - relations, - data, - generate, - - expectedContentDependencyKeys, - expectedExtraDependencyKeys, - missingContentDependencyKeys: newlyMissingContentDependencyKeys, - missingExtraDependencyKeys: newlyMissingExtraDependencyKeys, - invalidatingDependencyKeys: newlyInvalidatingDependencyKeys, - fulfilledDependencyKeys: newlyFulfilledDependencyKeys, - fulfilledDependencies: newlyFulfilledDependencies, - }); - - }; - - Object.assign(wrappedGenerate, { - contentDependencies: expectedContentDependencyKeys, - extraDependencies: expectedExtraDependencyKeys, - }); - - return wrappedGenerate; -} - -export function fulfillDependencies(dependencies, { - missingContentDependencyKeys, - missingExtraDependencyKeys, - invalidatingDependencyKeys, - fulfilledDependencyKeys, - fulfilledDependencies, -}) { - // This is a mutating function. Be aware: it WILL mutate the provided sets - // and objects EVEN IF there are errors. This function doesn't exit early, - // so all provided dependencies which don't have an associated error should - // be treated as fulfilled (this is reflected via fulfilledDependencyKeys). - - const errors = []; - - for (let [key, value] of Object.entries(dependencies)) { - if (fulfilledDependencyKeys.has(key)) { - errors.push(new Error(`Dependency ${key} is already fulfilled`)); - continue; - } - - const isContentKey = missingContentDependencyKeys.has(key); - const isExtraKey = missingExtraDependencyKeys.has(key); - - if (!isContentKey && !isExtraKey) { - errors.push(new Error(`Dependency ${key} is not expected`)); - continue; - } - - if (value === undefined) { - errors.push(new Error(`Dependency ${key} was provided undefined`)); - continue; - } - - const isContentFunction = - !!value?.[contentFunction.identifyingSymbol]; - - const isFulfilledContentFunction = - isContentFunction && value.fulfilled; - - if (isContentKey) { - if (!isContentFunction) { - errors.push(new Error(`Content dependency ${key} is not a content function (got ${value})`)); - continue; - } - - if (!isFulfilledContentFunction) { - invalidatingDependencyKeys.add(key); - } - - missingContentDependencyKeys.delete(key); - } else if (isExtraKey) { - if (isContentFunction) { - errors.push(new Error(`Extra dependency ${key} is a content function`)); - continue; - } - - missingExtraDependencyKeys.delete(key); - } - - fulfilledDependencyKeys.add(key); - fulfilledDependencies[key] = value; + }; } - if (!empty(errors)) { - throw new AggregateError(errors, `Errors fulfilling dependencies`); - } + return (...args) => generate(args); } export function getArgsForRelationsAndData(contentFunction, wikiData, ...args) { @@ -393,8 +196,6 @@ export function getRelationsTree(dependencies, contentFunctionName, wikiData, .. }; if (contentFunction.relations) { - const listedDependencies = new Set(contentFunction.contentDependencies); - // Note: "slots" here is a completely separate concept from HTML template // slots, which are handled completely within the content function. Here, // relation slots are just references to a position within the relations @@ -408,10 +209,6 @@ export function getRelationsTree(dependencies, contentFunctionName, wikiData, .. })(); const relationFunction = (name, ...args) => { - if (!listedDependencies.has(name)) { - throw new Error(`Called relation('${name}') but ${contentFunctionName} doesn't list that dependency`); - } - const relationSymbol = Symbol(relationSymbolMessage(name)); const traceError = new Error(); @@ -502,27 +299,11 @@ export function fillRelationsLayoutFromSlotResults(relationIdentifier, results, return recursive(layout); } -export function getNeededContentDependencyNames(contentDependencies, name) { - const set = new Set(); - - function recursive(name) { - const contentFunction = contentDependencies[name]; - for (const dependencyName of contentFunction?.contentDependencies ?? []) { - recursive(dependencyName); - } - set.add(name); - } - - recursive(name); - - return set; -} - export const decorateErrorWithRelationStack = (fn, traceStack) => decorateError(fn, caughtError => { let cause = caughtError; - for (const {name, args, traceError} of traceStack.slice().reverse()) { + for (const {name, args, traceError} of traceStack.toReversed()) { const nameText = colors.green(`"${name}"`); const namePart = `Error in relation(${nameText})`; @@ -579,65 +360,10 @@ export function quickEvaluate({ const flatTreeInfo = flattenRelationsTree(treeInfo); const {root, relationIdentifier, flatRelationSlots} = flatTreeInfo; - const neededContentDependencyNames = - getNeededContentDependencyNames(allContentDependencies, name); - - // Content functions aren't recursive, so by following the set above - // sequentually, we will always provide fulfilled content functions as the - // dependencies for later content functions. - const fulfilledContentDependencies = {}; - for (const name of neededContentDependencyNames) { - const unfulfilledContentFunction = allContentDependencies[name]; - if (!unfulfilledContentFunction) continue; - - const {contentDependencies, extraDependencies} = unfulfilledContentFunction; - - if (empty(contentDependencies) && empty(extraDependencies)) { - fulfilledContentDependencies[name] = unfulfilledContentFunction; - continue; - } - - const fulfillments = {}; - - for (const dependencyName of contentDependencies ?? []) { - if (dependencyName in fulfilledContentDependencies) { - fulfillments[dependencyName] = - fulfilledContentDependencies[dependencyName]; - } - } - - for (const dependencyName of extraDependencies ?? []) { - if (dependencyName in allExtraDependencies) { - fulfillments[dependencyName] = - allExtraDependencies[dependencyName]; - } - } - - fulfilledContentDependencies[name] = - unfulfilledContentFunction.fulfill(fulfillments); - } - - // There might still be unfulfilled content functions if dependencies weren't - // provided as part of allContentDependencies or allExtraDependencies. - // Catch and report these early, together in an aggregate error. - const unfulfilledErrors = []; - const unfulfilledNames = []; - for (const name of neededContentDependencyNames) { - const contentFunction = fulfilledContentDependencies[name]; - if (!contentFunction) continue; - if (!contentFunction.fulfilled) { - try { - contentFunction(); - } catch (error) { - error.message = `(${name}) ${error.message}`; - unfulfilledErrors.push(error); - unfulfilledNames.push(name); - } - } - } - - if (!empty(unfulfilledErrors)) { - throw new AggregateError(unfulfilledErrors, `Content functions unfulfilled (${unfulfilledNames.join(', ')})`); + allContentDependencies = {...allContentDependencies}; + for (const [name, contentFunction] of Object.entries(allContentDependencies)) { + allContentDependencies[name] = + contentFunction.bindExtraDependencies(allExtraDependencies); } const slotResults = {}; @@ -646,10 +372,9 @@ export function quickEvaluate({ const callDecorated = (fn, ...args) => decorateErrorWithRelationStack(fn, traceStack)(...args); - const contentFunction = fulfilledContentDependencies[name]; - + const contentFunction = allContentDependencies[name]; if (!contentFunction) { - throw new Error(`Content function ${name} unfulfilled or not listed`); + throw new Error(`Content function ${name} not listed`); } const generateArgs = []; diff --git a/src/content/dependencies/generateAbsoluteDatetimestamp.js b/src/content/dependencies/generateAbsoluteDatetimestamp.js index 930b6f13..d006374a 100644 --- a/src/content/dependencies/generateAbsoluteDatetimestamp.js +++ b/src/content/dependencies/generateAbsoluteDatetimestamp.js @@ -1,15 +1,12 @@ export default { - contentDependencies: [ - 'generateDatetimestampTemplate', - 'generateTooltip', - ], + data: (date, contextDate) => ({ + date, - extraDependencies: ['html', 'language'], - - data: (date) => - ({date}), + contextDate: + contextDate ?? null, + }), - relations: (relation) => ({ + relations: (relation, _date, _contextDate) => ({ template: relation('generateDatetimestampTemplate'), @@ -19,35 +16,74 @@ export default { slots: { style: { - validate: v => v.is('full', 'year'), + validate: v => v.is(...[ + 'full', + 'year', + 'minimal-difference', + 'year-difference', + ]), default: 'full', }, - - // Only has an effect for 'year' style. - tooltip: { - type: 'boolean', - default: false, - }, }, - generate: (data, relations, slots, {language}) => - relations.template.slots({ - mainContent: - (slots.style === 'full' - ? language.formatDate(data.date) - : slots.style === 'year' - ? data.date.getFullYear().toString() - : null), - - tooltip: - slots.tooltip && - slots.style === 'year' && - relations.tooltip.slots({ - content: - language.formatDate(data.date), - }), - - datetime: - data.date.toISOString(), - }), + generate(data, relations, slots, {html, language}) { + if (!data.date) { + return html.blank(); + } + + relations.template.setSlots({ + tooltip: relations.tooltip, + datetime: data.date.toISOString(), + }); + + let label = null; + let tooltip = null; + + switch (slots.style) { + case 'full': { + label = language.formatDate(data.date); + break; + } + + case 'year': { + label = language.formatYear(data.date); + tooltip = language.formatDate(data.date); + break; + } + + case 'minimal-difference': { + if (data.date.toDateString() === data.contextDate?.toDateString()) { + return html.blank(); + } + + if (data.date.getFullYear() === data.contextDate?.getFullYear()) { + label = language.formatMonthDay(data.date); + tooltip = language.formatDate(data.date); + } else { + label = language.formatYear(data.date); + tooltip = language.formatDate(data.date); + } + + break; + } + + case 'year-difference': { + if (data.date.toDateString() === data.contextDate?.toDateString()) { + return html.blank(); + } + + if (data.date.getFullYear() === data.contextDate?.getFullYear()) { + label = language.formatDate(data.date); + } else { + label = language.formatYear(data.date); + tooltip = language.formatDate(data.date); + } + } + } + + relations.template.setSlot('mainContent', label); + relations.tooltip.setSlot('content', tooltip); + + return relations.template; + }, }; diff --git a/src/content/dependencies/generateAdditionalFilesList.js b/src/content/dependencies/generateAdditionalFilesList.js index 68120b23..699c5f86 100644 --- a/src/content/dependencies/generateAdditionalFilesList.js +++ b/src/content/dependencies/generateAdditionalFilesList.js @@ -1,26 +1,19 @@ -import {stitchArrays} from '#sugar'; - export default { - extraDependencies: ['html'], + relations: (relation, additionalFiles) => ({ + chunks: + additionalFiles + .map(file => relation('generateAdditionalFilesListChunk', file)), + }), slots: { - chunks: { - validate: v => v.strictArrayOf(v.isHTML), - }, - - chunkItems: { - validate: v => v.strictArrayOf(v.isHTML), - }, + showFileSizes: {type: 'boolean', default: true}, }, - generate: (slots, {html}) => + generate: (relations, slots, {html}) => html.tag('ul', {class: 'additional-files-list'}, {[html.onlyIfContent]: true}, - stitchArrays({ - chunk: slots.chunks, - items: slots.chunkItems, - }).map(({chunk, items}) => - chunk.clone() - .slot('items', items))), + relations.chunks.map(chunk => chunk.slots({ + showFileSizes: slots.showFileSizes, + }))), }; diff --git a/src/content/dependencies/generateAdditionalFilesListChunk.js b/src/content/dependencies/generateAdditionalFilesListChunk.js index 507b2329..466a5d8d 100644 --- a/src/content/dependencies/generateAdditionalFilesListChunk.js +++ b/src/content/dependencies/generateAdditionalFilesListChunk.js @@ -1,46 +1,78 @@ +import {stitchArrays} from '#sugar'; + export default { - extraDependencies: ['html', 'language'], + relations: (relation, file) => ({ + description: + relation('transformContent', file.description), - slots: { - title: { - type: 'html', - mutable: false, - }, + links: + file.filenames + .map(filename => relation('linkAdditionalFile', file, filename)), + }), - description: { - type: 'html', - mutable: false, - }, + data: (file) => ({ + title: + file.title, + + paths: + file.paths, + }), - items: { - validate: v => v.looseArrayOf(v.isHTML), + slots: { + showFileSizes: { + type: 'boolean', }, }, - generate: (slots, {html, language}) => - language.encapsulate('releaseInfo.additionalFiles.entry', capsule => + generate: (data, relations, slots, {getSizeOfMediaFile, html, language, urls}) => + language.encapsulate('releaseInfo.additionalFiles', capsule => html.tag('li', html.tag('details', - html.isBlank(slots.items) && + html.isBlank(relations.links) && {open: true}, [ html.tag('summary', html.tag('span', - language.$(capsule, { + language.$(capsule, 'entry', { title: - html.tag('b', slots.title), + html.tag('b', data.title), }))), html.tag('ul', [ html.tag('li', {class: 'entry-description'}, {[html.onlyIfContent]: true}, - slots.description), - (html.isBlank(slots.items) + relations.description.slot('mode', 'inline')), + + (html.isBlank(relations.links) ? html.tag('li', - language.$(capsule, 'noFilesAvailable')) - : slots.items), + language.$(capsule, 'entry.noFilesAvailable')) + + : stitchArrays({ + link: relations.links, + path: data.paths, + }).map(({link, path}) => + html.tag('li', + language.encapsulate(capsule, 'file', workingCapsule => { + const workingOptions = {file: link}; + + if (slots.showFileSizes) { + const fileSize = + getSizeOfMediaFile( + urls + .from('media.root') + .to(...path)); + + if (fileSize) { + workingCapsule += '.withSize'; + workingOptions.size = + language.formatFileSize(fileSize); + } + } + + return language.$(workingCapsule, workingOptions); + })))), ]), ]))), }; diff --git a/src/content/dependencies/generateAdditionalFilesListChunkItem.js b/src/content/dependencies/generateAdditionalFilesListChunkItem.js deleted file mode 100644 index c37d6bb2..00000000 --- a/src/content/dependencies/generateAdditionalFilesListChunkItem.js +++ /dev/null @@ -1,30 +0,0 @@ -export default { - extraDependencies: ['html', 'language'], - - slots: { - fileLink: { - type: 'html', - mutable: false, - }, - - fileSize: { - validate: v => v.isWholeNumber, - }, - }, - - generate(slots, {html, language}) { - const itemParts = ['releaseInfo.additionalFiles.file']; - const itemOptions = {file: slots.fileLink}; - - if (slots.fileSize) { - itemParts.push('withSize'); - itemOptions.size = language.formatFileSize(slots.fileSize); - } - - const li = - html.tag('li', - language.$(...itemParts, itemOptions)); - - return li; - }, -}; diff --git a/src/content/dependencies/generateAdditionalNamesBox.js b/src/content/dependencies/generateAdditionalNamesBox.js index b7392dfd..6bd1ab42 100644 --- a/src/content/dependencies/generateAdditionalNamesBox.js +++ b/src/content/dependencies/generateAdditionalNamesBox.js @@ -1,18 +1,25 @@ export default { - contentDependencies: ['generateAdditionalNamesBoxItem'], - extraDependencies: ['html', 'language'], - relations: (relation, additionalNames) => ({ items: additionalNames .map(entry => relation('generateAdditionalNamesBoxItem', entry)), }), - generate: (relations, {html, language}) => + slots: { + alwaysVisible: { + type: 'boolean', + default: false, + }, + }, + + generate: (relations, slots, {html, language}) => html.tag('div', {id: 'additional-names-box'}, {class: 'drop'}, {[html.onlyIfContent]: true}, + slots.alwaysVisible && + {class: 'always-visible'}, + [ html.tag('p', {[html.onlyIfSiblings]: true}, diff --git a/src/content/dependencies/generateAdditionalNamesBoxItem.js b/src/content/dependencies/generateAdditionalNamesBoxItem.js index e3e59a34..a39711c1 100644 --- a/src/content/dependencies/generateAdditionalNamesBoxItem.js +++ b/src/content/dependencies/generateAdditionalNamesBoxItem.js @@ -1,7 +1,4 @@ export default { - contentDependencies: ['transformContent'], - extraDependencies: ['html', 'language'], - relations: (relation, entry) => ({ nameContent: relation('transformContent', entry.name), diff --git a/src/content/dependencies/generateAlbumAdditionalFilesList.js b/src/content/dependencies/generateAlbumAdditionalFilesList.js deleted file mode 100644 index ad17206f..00000000 --- a/src/content/dependencies/generateAlbumAdditionalFilesList.js +++ /dev/null @@ -1,96 +0,0 @@ -import {stitchArrays} from '#sugar'; - -export default { - contentDependencies: [ - 'generateAdditionalFilesList', - 'generateAdditionalFilesListChunk', - 'generateAdditionalFilesListChunkItem', - 'linkAlbumAdditionalFile', - 'transformContent', - ], - - extraDependencies: ['getSizeOfMediaFile', 'html', 'urls'], - - relations: (relation, album, additionalFiles) => ({ - list: - relation('generateAdditionalFilesList', additionalFiles), - - chunks: - additionalFiles - .map(() => relation('generateAdditionalFilesListChunk')), - - chunkDescriptions: - additionalFiles - .map(({description}) => - (description - ? relation('transformContent', description) - : null)), - - chunkItems: - additionalFiles - .map(({files}) => - (files ?? []) - .map(() => relation('generateAdditionalFilesListChunkItem'))), - - chunkItemFileLinks: - additionalFiles - .map(({files}) => - (files ?? []) - .map(file => relation('linkAlbumAdditionalFile', album, file))), - }), - - data: (album, additionalFiles) => ({ - albumDirectory: album.directory, - - chunkTitles: - additionalFiles - .map(({title}) => title), - - chunkItemLocations: - additionalFiles - .map(({files}) => files ?? []), - }), - - slots: { - showFileSizes: {type: 'boolean', default: true}, - }, - - generate: (data, relations, slots, {getSizeOfMediaFile, urls}) => - relations.list.slots({ - chunks: - stitchArrays({ - chunk: relations.chunks, - description: relations.chunkDescriptions, - title: data.chunkTitles, - }).map(({chunk, title, description}) => - chunk.slots({ - title, - description: - (description - ? description.slot('mode', 'inline') - : null), - })), - - chunkItems: - stitchArrays({ - items: relations.chunkItems, - fileLinks: relations.chunkItemFileLinks, - locations: data.chunkItemLocations, - }).map(({items, fileLinks, locations}) => - stitchArrays({ - item: items, - fileLink: fileLinks, - location: locations, - }).map(({item, fileLink, location}) => - item.slots({ - fileLink: fileLink, - fileSize: - (slots.showFileSizes - ? getSizeOfMediaFile( - urls - .from('media.root') - .to('media.albumAdditionalFile', data.albumDirectory, location)) - : 0), - }))), - }), -}; diff --git a/src/content/dependencies/generateAlbumArtInfoBox.js b/src/content/dependencies/generateAlbumArtInfoBox.js index 8c44c930..5491192a 100644 --- a/src/content/dependencies/generateAlbumArtInfoBox.js +++ b/src/content/dependencies/generateAlbumArtInfoBox.js @@ -1,7 +1,4 @@ export default { - contentDependencies: ['generateReleaseInfoContributionsLine'], - extraDependencies: ['html', 'language'], - relations: (relation, album) => ({ wallpaperArtistContributionsLine: (album.wallpaperArtwork diff --git a/src/content/dependencies/generateAlbumArtworkColumn.js b/src/content/dependencies/generateAlbumArtworkColumn.js index e6762463..5346e56b 100644 --- a/src/content/dependencies/generateAlbumArtworkColumn.js +++ b/src/content/dependencies/generateAlbumArtworkColumn.js @@ -1,38 +1,51 @@ export default { - contentDependencies: ['generateAlbumArtInfoBox', 'generateCoverArtwork'], - extraDependencies: ['html'], - - relations: (relation, album) => ({ - firstCover: + query: (album) => ({ + nonAttachingArtworkIndex: (album.hasCoverArt - ? relation('generateCoverArtwork', album.coverArtworks[0]) + ? album.coverArtworks.findIndex((artwork, index) => + index > 1 && + !artwork.attachAbove) : null), + }), + + relations: (relation, query, album) => ({ + firstCovers: + (album.hasCoverArt && query.nonAttachingArtworkIndex >= 1 + ? album.coverArtworks + .slice(0, query.nonAttachingArtworkIndex) + .map(artwork => relation('generateCoverArtwork', artwork)) + + : album.hasCoverArt + ? album.coverArtworks + .map(artwork => relation('generateCoverArtwork', artwork)) - restCovers: - (album.hasCoverArt - ? album.coverArtworks.slice(1).map(artwork => - relation('generateCoverArtwork', artwork)) : []), albumArtInfoBox: relation('generateAlbumArtInfoBox', album), + + restCovers: + (album.hasCoverArt && query.nonAttachingArtworkIndex >= 1 + ? album.coverArtworks + .slice(query.nonAttachingArtworkIndex) + .map(artwork => relation('generateCoverArtwork', artwork)) + + : []), }), - generate: (relations, {html}) => - html.tags([ - relations.firstCover?.slots({ + generate(relations, {html}) { + for (const cover of [...relations.firstCovers, ...relations.restCovers]) { + cover.setSlots({ showOriginDetails: true, showArtTagDetails: true, showReferenceDetails: true, - }), + }); + } + return html.tags([ + relations.firstCovers, relations.albumArtInfoBox, - - relations.restCovers.map(cover => - cover.slots({ - showOriginDetails: true, - showArtTagDetails: true, - showReferenceDetails: true, - })), - ]), + relations.restCovers, + ]); + }, }; diff --git a/src/content/dependencies/generateAlbumBanner.js b/src/content/dependencies/generateAlbumBanner.js index 3cc141bc..dce258de 100644 --- a/src/content/dependencies/generateAlbumBanner.js +++ b/src/content/dependencies/generateAlbumBanner.js @@ -1,7 +1,4 @@ export default { - contentDependencies: ['generateBanner'], - extraDependencies: ['html', 'language'], - relations(relation, album) { if (!album.hasBannerArt) { return {}; diff --git a/src/content/dependencies/generateAlbumCommentaryPage.js b/src/content/dependencies/generateAlbumCommentaryPage.js index 1e39b47d..4c203877 100644 --- a/src/content/dependencies/generateAlbumCommentaryPage.js +++ b/src/content/dependencies/generateAlbumCommentaryPage.js @@ -1,22 +1,6 @@ import {empty, stitchArrays} from '#sugar'; export default { - contentDependencies: [ - 'generateAlbumCommentarySidebar', - 'generateAlbumNavAccent', - 'generateAlbumSecondaryNav', - 'generateAlbumStyleRules', - 'generateCommentaryEntry', - 'generateContentHeading', - 'generateCoverArtwork', - 'generatePageLayout', - 'linkAlbum', - 'linkExternal', - 'linkTrack', - ], - - extraDependencies: ['html', 'language'], - query(album) { const query = {}; @@ -44,8 +28,8 @@ export default { relations.sidebar = relation('generateAlbumCommentarySidebar', album); - relations.albumStyleRules = - relation('generateAlbumStyleRules', album, null); + relations.albumStyleTags = + relation('generateAlbumStyleTags', album, null); relations.albumLink = relation('linkAlbum', album); @@ -151,7 +135,7 @@ export default { headingMode: 'sticky', color: data.color, - styleRules: [relations.albumStyleRules], + styleTags: relations.albumStyleTags, mainClasses: ['long-content'], mainContent: [ @@ -266,7 +250,10 @@ export default { }), })), - cover?.slots({mode: 'commentary'}), + cover?.slots({ + mode: 'commentary', + color: true, + }), trackDate && trackDate !== data.date && diff --git a/src/content/dependencies/generateAlbumCommentarySidebar.js b/src/content/dependencies/generateAlbumCommentarySidebar.js index 9ecec66d..4863f059 100644 --- a/src/content/dependencies/generateAlbumCommentarySidebar.js +++ b/src/content/dependencies/generateAlbumCommentarySidebar.js @@ -1,15 +1,6 @@ import {empty} from '#sugar'; export default { - contentDependencies: [ - 'generateAlbumSidebarTrackSection', - 'generatePageSidebar', - 'generatePageSidebarBox', - 'linkAlbum', - ], - - extraDependencies: ['html', 'language'], - relations: (relation, album) => ({ sidebar: relation('generatePageSidebar'), diff --git a/src/content/dependencies/generateAlbumGalleryAlbumGrid.js b/src/content/dependencies/generateAlbumGalleryAlbumGrid.js index 7f152871..f9cd027e 100644 --- a/src/content/dependencies/generateAlbumGalleryAlbumGrid.js +++ b/src/content/dependencies/generateAlbumGalleryAlbumGrid.js @@ -1,14 +1,6 @@ import {stitchArrays} from '#sugar'; export default { - contentDependencies: [ - 'generateCoverGrid', - 'image', - 'linkAlbum', - ], - - extraDependencies: ['html', 'language'], - query: (album) => ({ artworks: (album.hasCoverArt diff --git a/src/content/dependencies/generateAlbumGalleryCoverArtistsLine.js b/src/content/dependencies/generateAlbumGalleryCoverArtistsLine.js index 7dcdf6de..0322e227 100644 --- a/src/content/dependencies/generateAlbumGalleryCoverArtistsLine.js +++ b/src/content/dependencies/generateAlbumGalleryCoverArtistsLine.js @@ -1,7 +1,4 @@ export default { - contentDependencies: ['linkArtistGallery'], - extraDependencies: ['html', 'language'], - relations(relation, coverArtists) { return { coverArtistLinks: diff --git a/src/content/dependencies/generateAlbumGalleryNoTrackArtworksLine.js b/src/content/dependencies/generateAlbumGalleryNoTrackArtworksLine.js index ad99cb87..5932514e 100644 --- a/src/content/dependencies/generateAlbumGalleryNoTrackArtworksLine.js +++ b/src/content/dependencies/generateAlbumGalleryNoTrackArtworksLine.js @@ -1,6 +1,4 @@ export default { - extraDependencies: ['html', 'language'], - generate: ({html, language}) => html.tag('p', {class: 'quick-info'}, language.$('albumGalleryPage.noTrackArtworksLine')), diff --git a/src/content/dependencies/generateAlbumGalleryPage.js b/src/content/dependencies/generateAlbumGalleryPage.js index 2ba3b272..85b0fb74 100644 --- a/src/content/dependencies/generateAlbumGalleryPage.js +++ b/src/content/dependencies/generateAlbumGalleryPage.js @@ -2,21 +2,6 @@ import {stitchArrays, unique} from '#sugar'; import {getKebabCase} from '#wiki-data'; export default { - contentDependencies: [ - 'generateAlbumGalleryAlbumGrid', - 'generateAlbumGalleryNoTrackArtworksLine', - 'generateAlbumGalleryStatsLine', - 'generateAlbumGalleryTrackGrid', - 'generateAlbumNavAccent', - 'generateAlbumSecondaryNav', - 'generateAlbumStyleRules', - 'generateIntrapageDotSwitcher', - 'generatePageLayout', - 'linkAlbum', - ], - - extraDependencies: ['html', 'language'], - query(album) { const query = {}; @@ -46,8 +31,8 @@ export default { layout: relation('generatePageLayout'), - albumStyleRules: - relation('generateAlbumStyleRules', album, null), + albumStyleTags: + relation('generateAlbumStyleTags', album, null), albumLink: relation('linkAlbum', album), @@ -106,7 +91,7 @@ export default { headingMode: 'static', color: data.color, - styleRules: [relations.albumStyleRules], + styleTags: relations.albumStyleTags, mainClasses: ['top-index'], mainContent: [ diff --git a/src/content/dependencies/generateAlbumGalleryStatsLine.js b/src/content/dependencies/generateAlbumGalleryStatsLine.js index 75bffb36..75341937 100644 --- a/src/content/dependencies/generateAlbumGalleryStatsLine.js +++ b/src/content/dependencies/generateAlbumGalleryStatsLine.js @@ -1,38 +1,56 @@ import {getTotalDuration} from '#wiki-data'; export default { - extraDependencies: ['html', 'language'], - - data(album) { - return { - name: album.name, - date: album.date, - duration: getTotalDuration(album.tracks), - numTracks: album.tracks.length, - }; - }, - - generate(data, {html, language}) { - const parts = ['albumGalleryPage.statsLine']; - const options = {}; - - options.tracks = - html.tag('b', - language.countTracks(data.numTracks, {unit: true})); - - options.duration = - html.tag('b', - language.formatDuration(data.duration, {unit: true})); - - if (data.date) { - parts.push('withDate'); - options.date = - html.tag('b', - language.formatDate(data.date)); - } - - return ( - html.tag('p', {class: 'quick-info'}, - language.formatString(...parts, options))); - }, + data: (album) => ({ + date: + album.date, + + hideDuration: + album.hideDuration, + + duration: + (album.hideDuration + ? null + : getTotalDuration(album.tracks)), + + tracks: + (album.hideDuration + ? null + : album.tracks.length), + }), + + generate: (data, {html, language}) => + html.tag('p', {class: 'quick-info'}, + {[html.onlyIfContent]: true}, + + language.encapsulate('albumGalleryPage.statsLine', workingCapsule => { + const workingOptions = {}; + + if (data.hideDuration && !data.date) { + return html.blank(); + } + + if (!data.hideDuration) { + workingOptions.tracks = + html.tag('b', + language.countTracks(data.tracks, {unit: true})); + + workingOptions.duration = + html.tag('b', + language.formatDuration(data.duration, {unit: true})); + } + + if (data.date) { + workingCapsule += '.withDate'; + workingOptions.date = + html.tag('b', + language.formatDate(data.date)); + } + + if (data.hideDuration) { + workingCapsule += '.noDuration'; + } + + return language.$(workingCapsule, workingOptions); + })), }; diff --git a/src/content/dependencies/generateAlbumGalleryTrackGrid.js b/src/content/dependencies/generateAlbumGalleryTrackGrid.js index fb5ed7ea..a50448c6 100644 --- a/src/content/dependencies/generateAlbumGalleryTrackGrid.js +++ b/src/content/dependencies/generateAlbumGalleryTrackGrid.js @@ -1,16 +1,6 @@ import {compareArrays, stitchArrays} from '#sugar'; export default { - contentDependencies: [ - 'generateAlbumGalleryCoverArtistsLine', - 'generateCoverGrid', - 'image', - 'linkAlbum', - 'linkTrack', - ], - - extraDependencies: ['html', 'language'], - query(album, label) { const query = {}; @@ -77,6 +67,9 @@ export default { ? artwork.artistContribs .map(contrib => contrib.artist.name) : null)), + + allWarnings: + query.artworks.flatMap(artwork => artwork?.contentWarnings), }), slots: { @@ -117,6 +110,9 @@ export default { artists: language.formatUnitList(artists), })), + + revealAllWarnings: + data.allWarnings, }), ]), }; diff --git a/src/content/dependencies/generateAlbumInfoPage.js b/src/content/dependencies/generateAlbumInfoPage.js index d0788523..a27074ff 100644 --- a/src/content/dependencies/generateAlbumInfoPage.js +++ b/src/content/dependencies/generateAlbumInfoPage.js @@ -1,33 +1,12 @@ import {empty} from '#sugar'; export default { - contentDependencies: [ - 'generateAdditionalNamesBox', - 'generateAlbumAdditionalFilesList', - 'generateAlbumArtworkColumn', - 'generateAlbumBanner', - 'generateAlbumNavAccent', - 'generateAlbumReleaseInfo', - 'generateAlbumSecondaryNav', - 'generateAlbumSidebar', - 'generateAlbumSocialEmbed', - 'generateAlbumStyleRules', - 'generateAlbumTrackList', - 'generateCommentaryEntry', - 'generateContentHeading', - 'generatePageLayout', - 'linkAlbumCommentary', - 'linkAlbumGallery', - ], - - extraDependencies: ['html', 'language'], - relations: (relation, album) => ({ layout: relation('generatePageLayout'), - albumStyleRules: - relation('generateAlbumStyleRules', album, null), + albumStyleTags: + relation('generateAlbumStyleTags', album, null), socialEmbed: relation('generateAlbumSocialEmbed', album), @@ -64,25 +43,30 @@ export default { : null), commentaryLink: - ([album, ...album.tracks].some(({commentary}) => !empty(commentary)) + (album.tracks.some(track => !empty(track.commentary)) ? relation('linkAlbumCommentary', album) : null), + readCommentaryLine: + relation('generateReadCommentaryLine', album), + trackList: relation('generateAlbumTrackList', album), additionalFilesList: - relation('generateAlbumAdditionalFilesList', - album, - album.additionalFiles), + relation('generateAdditionalFilesList', album.additionalFiles), + + commentaryContentHeading: + relation('generateCommentaryContentHeading', album), artistCommentaryEntries: album.commentary .map(entry => relation('generateCommentaryEntry', entry)), - creditSourceEntries: - album.creditSources - .map(entry => relation('generateCommentaryEntry', entry)), + creditingSourcesSection: + relation('generateCollapsedContentEntrySection', + album.creditingSources, + album), }), data: (album) => ({ @@ -106,7 +90,7 @@ export default { color: data.color, headingMode: 'sticky', - styleRules: [relations.albumStyleRules], + styleTags: relations.albumStyleTags, additionalNames: relations.additionalNamesBox, @@ -158,12 +142,16 @@ export default { : html.blank()), - !html.isBlank(relations.creditSourceEntries) && - language.encapsulate(capsule, 'readCreditSources', capsule => + !relations.commentaryLink && + !html.isBlank(relations.artistCommentaryEntries) && + relations.readCommentaryLine, + + !html.isBlank(relations.creditingSourcesSection) && + language.encapsulate(capsule, 'readCreditingSources', capsule => language.$(capsule, { link: html.tag('a', - {href: '#credit-sources'}, + {href: '#crediting-sources'}, language.$(capsule, 'link')), })), ])), @@ -172,14 +160,14 @@ export default { html.tag('p', {[html.onlyIfContent]: true}, - {[html.joinChildren]: html.tag('br')}, - language.encapsulate('releaseInfo', capsule => [ - language.$(capsule, 'addedToWiki', { - [language.onlyIfOptions]: ['date'], - date: language.formatDate(data.dateAddedToWiki), - }), - ])), + language.$('releaseInfo.addedToWiki', { + [language.onlyIfOptions]: ['date'], + date: language.formatDate(data.dateAddedToWiki), + })), + + !html.isBlank(relations.artistCommentaryEntries) && + html.tag('hr', {class: 'main-separator'}), language.encapsulate('releaseInfo.additionalFiles', capsule => html.tags([ @@ -193,24 +181,14 @@ export default { ])), html.tags([ - relations.contentHeading.clone() - .slots({ - attributes: {id: 'artist-commentary'}, - title: language.$('misc.artistCommentary'), - }), - + relations.commentaryContentHeading, relations.artistCommentaryEntries, ]), - html.tags([ - relations.contentHeading.clone() - .slots({ - attributes: {id: 'credit-sources'}, - title: language.$('misc.creditSources'), - }), - - relations.creditSourceEntries, - ]), + relations.creditingSourcesSection.slots({ + id: 'crediting-sources', + string: 'misc.creditingSources', + }), ], navLinkStyle: 'hierarchical', diff --git a/src/content/dependencies/generateAlbumNavAccent.js b/src/content/dependencies/generateAlbumNavAccent.js index 432c5f3d..237120f3 100644 --- a/src/content/dependencies/generateAlbumNavAccent.js +++ b/src/content/dependencies/generateAlbumNavAccent.js @@ -1,17 +1,6 @@ import {atOffset, empty} from '#sugar'; export default { - contentDependencies: [ - 'generateInterpageDotSwitcher', - 'generateNextLink', - 'generatePreviousLink', - 'linkTrack', - 'linkAlbumCommentary', - 'linkAlbumGallery', - ], - - extraDependencies: ['html', 'language'], - query(album, track) { const query = {}; @@ -64,9 +53,8 @@ export default { hasMultipleTracks: album.tracks.length > 1, - commentaryPageIsStub: - [album, ...album.tracks] - .every(({commentary}) => empty(commentary)), + hasSubstantialCommentaryPage: + album.tracks.some(track => !empty(track.commentary)), galleryIsStub: album.tracks.every(t => !t.hasUniqueCoverArt), @@ -97,14 +85,16 @@ export default { relations.nextLink.slot('link', relations.nextTrackLink); const galleryLink = - (!data.galleryIsStub || slots.currentExtra === 'gallery') && + (!data.galleryIsStub || + slots.currentExtra === 'gallery') && relations.albumGalleryLink.slots({ attributes: {class: slots.currentExtra === 'gallery' && 'current'}, content: language.$(albumNavCapsule, 'gallery'), }); const commentaryLink = - (!data.commentaryPageIsStub || slots.currentExtra === 'commentary') && + (data.hasSubstantialCommentaryPage || + slots.currentExtra === 'commentary') && relations.albumCommentaryLink.slots({ attributes: {class: slots.currentExtra === 'commentary' && 'current'}, content: language.$(albumNavCapsule, 'commentary'), diff --git a/src/content/dependencies/generateAlbumReferencedArtworksPage.js b/src/content/dependencies/generateAlbumReferencedArtworksPage.js index 7586393c..e4022f0d 100644 --- a/src/content/dependencies/generateAlbumReferencedArtworksPage.js +++ b/src/content/dependencies/generateAlbumReferencedArtworksPage.js @@ -1,19 +1,10 @@ export default { - contentDependencies: [ - 'generateAlbumStyleRules', - 'generateBackToAlbumLink', - 'generateReferencedArtworksPage', - 'linkAlbum', - ], - - extraDependencies: ['html', 'language'], - relations: (relation, album) => ({ page: relation('generateReferencedArtworksPage', album.coverArtworks[0]), - albumStyleRules: - relation('generateAlbumStyleRules', album, null), + albumStyleTags: + relation('generateAlbumStyleTags', album, null), albumLink: relation('linkAlbum', album), @@ -35,7 +26,7 @@ export default { data.name, }), - styleRules: [relations.albumStyleRules], + styleTags: relations.albumStyleTags, navLinks: [ {auto: 'home'}, diff --git a/src/content/dependencies/generateAlbumReferencingArtworksPage.js b/src/content/dependencies/generateAlbumReferencingArtworksPage.js index d072d2f6..0dc1bf15 100644 --- a/src/content/dependencies/generateAlbumReferencingArtworksPage.js +++ b/src/content/dependencies/generateAlbumReferencingArtworksPage.js @@ -1,19 +1,10 @@ export default { - contentDependencies: [ - 'generateAlbumStyleRules', - 'generateBackToAlbumLink', - 'generateReferencingArtworksPage', - 'linkAlbum', - ], - - extraDependencies: ['html', 'language'], - relations: (relation, album) => ({ page: relation('generateReferencingArtworksPage', album.coverArtworks[0]), - albumStyleRules: - relation('generateAlbumStyleRules', album, null), + albumStyleTags: + relation('generateAlbumStyleTags', album, null), albumLink: relation('linkAlbum', album), @@ -35,7 +26,7 @@ export default { data.name, }), - styleRules: [relations.albumStyleRules], + styleTags: relations.albumStyleTags, navLinks: [ {auto: 'home'}, diff --git a/src/content/dependencies/generateAlbumReleaseInfo.js b/src/content/dependencies/generateAlbumReleaseInfo.js index 0abb412c..4cec4120 100644 --- a/src/content/dependencies/generateAlbumReleaseInfo.js +++ b/src/content/dependencies/generateAlbumReleaseInfo.js @@ -1,28 +1,14 @@ import {accumulateSum, empty} from '#sugar'; export default { - contentDependencies: [ - 'generateReleaseInfoContributionsLine', - 'linkExternal', - ], - - extraDependencies: ['html', 'language'], - relations(relation, album) { const relations = {}; relations.artistContributionsLine = relation('generateReleaseInfoContributionsLine', album.artistContribs); - relations.wallpaperArtistContributionsLine = - relation('generateReleaseInfoContributionsLine', album.wallpaperArtistContribs); - - relations.bannerArtistContributionsLine = - relation('generateReleaseInfoContributionsLine', album.bannerArtistContribs); - - relations.externalLinks = - album.urls.map(url => - relation('linkExternal', url)); + relations.listenLine = + relation('generateReleaseInfoListenLine', album); return relations; }, @@ -43,7 +29,7 @@ export default { .map(track => track.duration) .filter(value => value > 0); - if (empty(durationTerms)) { + if (empty(durationTerms) || album.hideDuration) { data.duration = null; data.durationApproximate = null; } else { @@ -87,21 +73,16 @@ export default { html.tag('p', {[html.onlyIfContent]: true}, - language.$(capsule, 'listenOn', { - [language.onlyIfOptions]: ['links'], - - links: - language.formatDisjunctionList( - relations.externalLinks - .map(link => - link.slot('context', [ - 'album', - (data.numTracks === 0 - ? 'albumNoTracks' - : data.numTracks === 1 - ? 'albumOneTrack' - : 'albumMultipleTracks'), - ]))), + relations.listenLine.slots({ + context: [ + 'album', + + (data.numTracks === 0 + ? 'albumNoTracks' + : data.numTracks === 1 + ? 'albumOneTrack' + : 'albumMultipleTracks'), + ], })), ])), }; diff --git a/src/content/dependencies/generateAlbumSecondaryNav.js b/src/content/dependencies/generateAlbumSecondaryNav.js index bfa48f03..2140bfdb 100644 --- a/src/content/dependencies/generateAlbumSecondaryNav.js +++ b/src/content/dependencies/generateAlbumSecondaryNav.js @@ -1,15 +1,6 @@ import {stitchArrays} from '#sugar'; export default { - contentDependencies: [ - 'generateAlbumSecondaryNavGroupPart', - 'generateAlbumSecondaryNavSeriesPart', - 'generateDotSwitcherTemplate', - 'generateSecondaryNav', - ], - - extraDependencies: ['html', 'wikiData'], - sprawl: ({groupData}) => ({ // TODO: Series aren't their own things, so we access them weirdly. seriesData: diff --git a/src/content/dependencies/generateAlbumSecondaryNavGroupPart.js b/src/content/dependencies/generateAlbumSecondaryNavGroupPart.js index 22dfa51c..2f08804b 100644 --- a/src/content/dependencies/generateAlbumSecondaryNavGroupPart.js +++ b/src/content/dependencies/generateAlbumSecondaryNavGroupPart.js @@ -2,15 +2,6 @@ import {sortChronologically} from '#sort'; import {atOffset} from '#sugar'; export default { - contentDependencies: [ - 'generateColorStyleAttribute', - 'generateSecondaryNavParentSiblingsPart', - 'linkAlbumDynamically', - 'linkGroup', - ], - - extraDependencies: ['html'], - query(group, album) { const query = {}; diff --git a/src/content/dependencies/generateAlbumSecondaryNavSeriesPart.js b/src/content/dependencies/generateAlbumSecondaryNavSeriesPart.js index 16f205e3..ee180f16 100644 --- a/src/content/dependencies/generateAlbumSecondaryNavSeriesPart.js +++ b/src/content/dependencies/generateAlbumSecondaryNavSeriesPart.js @@ -1,15 +1,6 @@ import {atOffset} from '#sugar'; export default { - contentDependencies: [ - 'generateColorStyleAttribute', - 'generateSecondaryNavParentSiblingsPart', - 'linkAlbumDynamically', - 'linkGroup', - ], - - extraDependencies: ['html', 'language'], - query(series, album) { const query = {}; diff --git a/src/content/dependencies/generateAlbumSidebar.js b/src/content/dependencies/generateAlbumSidebar.js index 7cf689cc..83a637b0 100644 --- a/src/content/dependencies/generateAlbumSidebar.js +++ b/src/content/dependencies/generateAlbumSidebar.js @@ -2,17 +2,6 @@ import {sortAlbumsTracksChronologically} from '#sort'; import {stitchArrays, transposeArrays} from '#sugar'; export default { - contentDependencies: [ - 'generateAlbumSidebarGroupBox', - 'generateAlbumSidebarSeriesBox', - 'generateAlbumSidebarTrackListBox', - 'generatePageSidebar', - 'generatePageSidebarConjoinedBox', - 'generateTrackReleaseBox', - ], - - extraDependencies: ['html', 'wikiData'], - sprawl: ({groupData}) => ({ // TODO: Series aren't their own things, so we access them weirdly. seriesData: @@ -46,7 +35,8 @@ export default { const allReleaseAlbums = sortAlbumsTracksChronologically( - Array.from(albumTrackMap.keys())); + Array.from(albumTrackMap.keys()), + {getDate: album => albumTrackMap.get(album).date}); const currentReleaseIndex = allReleaseAlbums.indexOf(track.album); @@ -108,39 +98,65 @@ export default { : null), }), - data: (_query, _sprawl, _album, track) => ({ + data: (_query, _sprawl, album, track) => ({ isAlbumPage: !track, isTrackPage: !!track, + + albumStyle: album.style, }), generate(data, relations, {html}) { + const presentGroupsLikeAlbum = + data.isAlbumPage || + data.albumStyle === 'single'; + for (const box of [ ...relations.groupBoxes, ...relations.seriesBoxes.flat(), ...relations.disconnectedSeriesBoxes, ]) { - box.setSlot('mode', - data.isAlbumPage ? 'album' : 'track'); + box.setSlot('mode', presentGroupsLikeAlbum ? 'album' : 'track'); } + const groupBoxes = + (presentGroupsLikeAlbum + ? [ + relations.disconnectedSeriesBoxes, + + stitchArrays({ + groupBox: relations.groupBoxes, + seriesBoxes: relations.seriesBoxes, + }).map(({groupBox, seriesBoxes}) => [ + groupBox, + seriesBoxes.map(seriesBox => [ + html.tag('div', + {class: 'sidebar-box-joiner'}, + {class: 'collapsible'}), + seriesBox, + ]), + ]), + ] + : [ + relations.conjoinedBox.slots({ + attributes: {class: 'conjoined-group-sidebar-box'}, + boxes: + ([relations.disconnectedSeriesBoxes, + stitchArrays({ + groupBox: relations.groupBoxes, + seriesBoxes: relations.seriesBoxes, + }).flatMap(({groupBox, seriesBoxes}) => [ + groupBox, + ...seriesBoxes, + ]), + ]).flat() + .map(box => box.content), /* TODO: Kludge. */ + }) + ]); + return relations.sidebar.slots({ boxes: [ - data.isAlbumPage && [ - relations.disconnectedSeriesBoxes, - - stitchArrays({ - groupBox: relations.groupBoxes, - seriesBoxes: relations.seriesBoxes, - }).map(({groupBox, seriesBoxes}) => [ - groupBox, - seriesBoxes.map(seriesBox => [ - html.tag('div', - {class: 'sidebar-box-joiner'}, - {class: 'collapsible'}), - seriesBox, - ]), - ]), - ], + data.isAlbumPage && + groupBoxes, data.isTrackPage && relations.earlierTrackReleaseBoxes, @@ -151,20 +167,7 @@ export default { relations.laterTrackReleaseBoxes, data.isTrackPage && - relations.conjoinedBox.slots({ - attributes: {class: 'conjoined-group-sidebar-box'}, - boxes: - ([relations.disconnectedSeriesBoxes, - stitchArrays({ - groupBox: relations.groupBoxes, - seriesBoxes: relations.seriesBoxes, - }).flatMap(({groupBox, seriesBoxes}) => [ - groupBox, - ...seriesBoxes, - ]), - ]).flat() - .map(box => box.content), /* TODO: Kludge. */ - }), + groupBoxes, ], }); }, diff --git a/src/content/dependencies/generateAlbumSidebarGroupBox.js b/src/content/dependencies/generateAlbumSidebarGroupBox.js index f3be74f7..0a9c0db9 100644 --- a/src/content/dependencies/generateAlbumSidebarGroupBox.js +++ b/src/content/dependencies/generateAlbumSidebarGroupBox.js @@ -2,16 +2,6 @@ import {sortChronologically} from '#sort'; import {atOffset} from '#sugar'; export default { - contentDependencies: [ - 'generatePageSidebarBox', - 'linkAlbum', - 'linkExternal', - 'linkGroup', - 'transformContent', - ], - - extraDependencies: ['html', 'language'], - query(album, group) { const query = {}; diff --git a/src/content/dependencies/generateAlbumSidebarSeriesBox.js b/src/content/dependencies/generateAlbumSidebarSeriesBox.js index 37616cb2..22f1fe72 100644 --- a/src/content/dependencies/generateAlbumSidebarSeriesBox.js +++ b/src/content/dependencies/generateAlbumSidebarSeriesBox.js @@ -1,15 +1,6 @@ import {atOffset} from '#sugar'; export default { - contentDependencies: [ - 'generatePageSidebarBox', - 'linkAlbum', - 'linkGroup', - 'transformContent', - ], - - extraDependencies: ['html', 'language'], - query(album, series) { const query = {}; diff --git a/src/content/dependencies/generateAlbumSidebarTrackListBox.js b/src/content/dependencies/generateAlbumSidebarTrackListBox.js index 3a244e3a..4e9437c9 100644 --- a/src/content/dependencies/generateAlbumSidebarTrackListBox.js +++ b/src/content/dependencies/generateAlbumSidebarTrackListBox.js @@ -1,12 +1,4 @@ export default { - contentDependencies: [ - 'generateAlbumSidebarTrackSection', - 'generatePageSidebarBox', - 'linkAlbum', - ], - - extraDependencies: ['html'], - relations: (relation, album, track) => ({ box: relation('generatePageSidebarBox'), @@ -24,7 +16,9 @@ export default { attributes: {class: 'track-list-sidebar-box'}, content: [ - html.tag('h1', relations.albumLink), + html.tag('h1', {[html.onlyIfSiblings]: true}, + relations.albumLink), + relations.trackSections, ], }) diff --git a/src/content/dependencies/generateAlbumSidebarTrackSection.js b/src/content/dependencies/generateAlbumSidebarTrackSection.js index dae5fa03..68281bfe 100644 --- a/src/content/dependencies/generateAlbumSidebarTrackSection.js +++ b/src/content/dependencies/generateAlbumSidebarTrackSection.js @@ -1,9 +1,6 @@ import {empty, stitchArrays} from '#sugar'; export default { - contentDependencies: ['linkTrack'], - extraDependencies: ['getColors', 'html', 'language'], - relations(relation, album, track, trackSection) { const relations = {}; @@ -22,10 +19,12 @@ export default { !empty(trackSection.tracks); data.isTrackPage = !!track; + data.albumStyle = album.style; data.name = trackSection.name; data.color = trackSection.color; data.isDefaultTrackSection = trackSection.isDefaultTrackSection; + data.hasSiblingSections = album.trackSections.length > 1; data.firstTrackNumber = (data.hasTrackNumbers @@ -115,6 +114,21 @@ export default { : trackLink), }))); + const list = + (data.hasTrackNumbers + ? html.tag('ol', + {start: data.firstTrackNumber}, + trackListItems) + : html.tag('ul', trackListItems)); + + if (data.albumStyle === 'single' && !data.hasSiblingSections) { + if (trackListItems.length <= 1) { + return html.blank(); + } else { + return list; + } + } + return html.tag('details', data.includesCurrentTrack && {class: 'current'}, @@ -157,11 +171,7 @@ export default { return language.$(workingCapsule, workingOptions); })))), - (data.hasTrackNumbers - ? html.tag('ol', - {start: data.firstTrackNumber}, - trackListItems) - : html.tag('ul', trackListItems)), + list, ]); }, }; diff --git a/src/content/dependencies/generateAlbumSocialEmbed.js b/src/content/dependencies/generateAlbumSocialEmbed.js index e28a3fd0..1200ec8b 100644 --- a/src/content/dependencies/generateAlbumSocialEmbed.js +++ b/src/content/dependencies/generateAlbumSocialEmbed.js @@ -1,13 +1,6 @@ import {empty} from '#sugar'; export default { - contentDependencies: [ - 'generateSocialEmbed', - 'generateAlbumSocialEmbedDescription', - ], - - extraDependencies: ['absoluteTo', 'language'], - relations(relation, album) { return { socialEmbed: diff --git a/src/content/dependencies/generateAlbumSocialEmbedDescription.js b/src/content/dependencies/generateAlbumSocialEmbedDescription.js index 69c39c3a..db6da5b7 100644 --- a/src/content/dependencies/generateAlbumSocialEmbedDescription.js +++ b/src/content/dependencies/generateAlbumSocialEmbedDescription.js @@ -1,8 +1,6 @@ import {accumulateSum} from '#sugar'; export default { - extraDependencies: ['language'], - data: (album) => ({ duration: accumulateSum(album.tracks, track => track.duration), diff --git a/src/content/dependencies/generateAlbumStyleRules.js b/src/content/dependencies/generateAlbumStyleRules.js deleted file mode 100644 index 6bfcc62e..00000000 --- a/src/content/dependencies/generateAlbumStyleRules.js +++ /dev/null @@ -1,107 +0,0 @@ -import {empty, stitchArrays} from '#sugar'; - -export default { - extraDependencies: ['to'], - - data(album, track) { - const data = {}; - - data.hasWallpaper = !empty(album.wallpaperArtistContribs); - data.hasBanner = !empty(album.bannerArtistContribs); - - if (data.hasWallpaper) { - if (!empty(album.wallpaperParts)) { - data.wallpaperMode = 'parts'; - - data.wallpaperPaths = - album.wallpaperParts.map(part => - (part.asset - ? ['media.albumWallpaperPart', album.directory, part.asset] - : null)); - - data.wallpaperStyles = - album.wallpaperParts.map(part => part.style); - } else { - data.wallpaperMode = 'one'; - data.wallpaperPath = ['media.albumWallpaper', album.directory, album.wallpaperFileExtension]; - data.wallpaperStyle = album.wallpaperStyle; - } - } - - if (data.hasBanner) { - data.hasBannerStyle = !!album.bannerStyle; - data.bannerStyle = album.bannerStyle; - } - - data.albumDirectory = album.directory; - - if (track) { - data.trackDirectory = track.directory; - } - - return data; - }, - - generate(data, {to}) { - const indent = parts => - (parts ?? []) - .filter(Boolean) - .join('\n') - .split('\n') - .map(line => ' '.repeat(4) + line) - .join('\n'); - - const rule = (selector, parts) => - (!empty(parts.filter(Boolean)) - ? [`${selector} {`, indent(parts), `}`] - : []); - - const oneWallpaperRule = - data.wallpaperMode === 'one' && - rule(`body::before`, [ - `background-image: url("${to(...data.wallpaperPath)}");`, - data.wallpaperStyle, - ]); - - const wallpaperPartRules = - data.wallpaperMode === 'parts' && - stitchArrays({ - path: data.wallpaperPaths, - style: data.wallpaperStyles, - }).map(({path, style}, index) => - rule(`.wallpaper-part:nth-child(${index + 1})`, [ - path && `background-image: url("${to(...path)}");`, - style, - ])); - - const nukeBasicWallpaperRule = - data.wallpaperMode === 'parts' && - rule(`body::before`, ['display: none']); - - const wallpaperRules = [ - oneWallpaperRule, - ...wallpaperPartRules || [], - nukeBasicWallpaperRule, - ]; - - const bannerRule = - data.hasBanner && - rule(`#banner img`, [ - data.bannerStyle, - ]); - - const dataRule = - rule(`:root`, [ - data.albumDirectory && - `--album-directory: ${data.albumDirectory};`, - data.trackDirectory && - `--track-directory: ${data.trackDirectory};`, - ]); - - return ( - [...wallpaperRules, bannerRule, dataRule] - .filter(Boolean) - .flat() - .join('\n')); - }, -}; diff --git a/src/content/dependencies/generateAlbumStyleTags.js b/src/content/dependencies/generateAlbumStyleTags.js new file mode 100644 index 00000000..caf21dc4 --- /dev/null +++ b/src/content/dependencies/generateAlbumStyleTags.js @@ -0,0 +1,62 @@ +import {empty} from '#sugar'; + +export default { + relations: (relation, album, _track) => ({ + styleTag: + relation('generateStyleTag'), + + wallpaperStyleTag: + relation('generateAlbumWallpaperStyleTag', album), + }), + + data(album, track) { + const data = {}; + + data.hasBanner = !empty(album.bannerArtistContribs); + + if (data.hasBanner) { + data.hasBannerStyle = !!album.bannerStyle; + data.bannerStyle = album.bannerStyle; + } + + data.albumDirectory = album.directory; + + if (track) { + data.trackDirectory = track.directory; + } + + return data; + }, + + generate: (data, relations, {html}) => + html.tags([ + relations.wallpaperStyleTag, + + relations.styleTag.clone().slots({ + attributes: {class: 'album-banner-style'}, + + rules: [ + data.hasBanner && { + select: '#banner img', + declare: [data.bannerStyle], + }, + ], + }), + + relations.styleTag.clone().slots({ + attributes: {class: 'album-directory-style'}, + + rules: [ + { + select: ':root', + declare: [ + data.albumDirectory && + `--album-directory: ${data.albumDirectory};`, + data.trackDirectory && + `--track-directory: ${data.trackDirectory};`, + ], + }, + ] + }), + ], {[html.joinChildren]: ''}), +}; diff --git a/src/content/dependencies/generateAlbumTrackList.js b/src/content/dependencies/generateAlbumTrackList.js index 0a949ded..93cb420b 100644 --- a/src/content/dependencies/generateAlbumTrackList.js +++ b/src/content/dependencies/generateAlbumTrackList.js @@ -35,14 +35,6 @@ function getDisplayMode(album) { } export default { - contentDependencies: [ - 'generateAlbumTrackListItem', - 'generateContentHeading', - 'transformContent', - ], - - extraDependencies: ['html', 'language'], - query(album) { return { displayMode: getDisplayMode(album), diff --git a/src/content/dependencies/generateAlbumTrackListItem.js b/src/content/dependencies/generateAlbumTrackListItem.js index 44297c15..68722a83 100644 --- a/src/content/dependencies/generateAlbumTrackListItem.js +++ b/src/content/dependencies/generateAlbumTrackListItem.js @@ -1,7 +1,4 @@ export default { - contentDependencies: ['generateTrackListItem'], - extraDependencies: ['html'], - query: (track, album) => ({ trackHasDuration: !!track.duration, @@ -20,7 +17,7 @@ export default { item: relation('generateTrackListItem', track, - track.album.artistContribs), + track.album.trackArtistContribs), }), data: (query, track, album) => ({ @@ -43,7 +40,7 @@ export default { generate: (data, relations, slots) => relations.item.slots({ - showArtists: true, + showArtists: 'auto', showDuration: (slots.collapseDurationScope === 'track' diff --git a/src/content/dependencies/generateAlbumWallpaperStyleTag.js b/src/content/dependencies/generateAlbumWallpaperStyleTag.js new file mode 100644 index 00000000..b3f74716 --- /dev/null +++ b/src/content/dependencies/generateAlbumWallpaperStyleTag.js @@ -0,0 +1,35 @@ +export default { + relations: (relation, album) => ({ + wallpaperStyleTag: + (album.hasWallpaperArt + ? relation('generateWallpaperStyleTag') + : null), + }), + + data: (album) => ({ + singleWallpaperPath: + ['media.albumWallpaper', album.directory, album.wallpaperFileExtension], + + singleWallpaperStyle: + album.wallpaperStyle, + + wallpaperPartPaths: + album.wallpaperParts.map(part => + (part.asset + ? ['media.albumWallpaperPart', album.directory, part.asset] + : null)), + + wallpaperPartStyles: + album.wallpaperParts.map(part => part.style), + }), + + generate: (data, relations, {html}) => + (relations.wallpaperStyleTag + ? relations.wallpaperStyleTag.slots({ + singleWallpaperPath: data.singleWallpaperPath, + singleWallpaperStyle: data.singleWallpaperStyle, + wallpaperPartPaths: data.wallpaperPartPaths, + wallpaperPartStyles: data.wallpaperPartStyles, + }) + : html.blank()), +}; diff --git a/src/content/dependencies/generateArtTagAncestorDescendantMapList.js b/src/content/dependencies/generateArtTagAncestorDescendantMapList.js index 80d19b5a..37a32a94 100644 --- a/src/content/dependencies/generateArtTagAncestorDescendantMapList.js +++ b/src/content/dependencies/generateArtTagAncestorDescendantMapList.js @@ -6,9 +6,6 @@ import { } from '#sugar'; export default { - contentDependencies: ['linkArtTagDynamically'], - extraDependencies: ['html', 'language'], - // Recursion ain't too pretty! query(ancestorArtTag, targetArtTag) { diff --git a/src/content/dependencies/generateArtTagGalleryPage.js b/src/content/dependencies/generateArtTagGalleryPage.js index cfd6d03e..f20babba 100644 --- a/src/content/dependencies/generateArtTagGalleryPage.js +++ b/src/content/dependencies/generateArtTagGalleryPage.js @@ -2,22 +2,6 @@ import {sortArtworksChronologically} from '#sort'; import {empty, stitchArrays, unique} from '#sugar'; export default { - contentDependencies: [ - 'generateAdditionalNamesBox', - 'generateArtTagGalleryPageFeaturedLine', - 'generateArtTagGalleryPageShowingLine', - 'generateArtTagNavLinks', - 'generateCoverGrid', - 'generatePageLayout', - 'generateQuickDescription', - 'image', - 'linkAnythingMan', - 'linkArtTagGallery', - 'linkExternal', - ], - - extraDependencies: ['html', 'language', 'wikiData'], - sprawl({wikiInfo}) { return { enableListings: wikiInfo.enableListings, diff --git a/src/content/dependencies/generateArtTagGalleryPageFeaturedLine.js b/src/content/dependencies/generateArtTagGalleryPageFeaturedLine.js index b4620fa4..8593cc21 100644 --- a/src/content/dependencies/generateArtTagGalleryPageFeaturedLine.js +++ b/src/content/dependencies/generateArtTagGalleryPageFeaturedLine.js @@ -1,6 +1,4 @@ export default { - extraDependencies: ['html', 'language'], - slots: { showing: { validate: v => v.is('all', 'direct', 'indirect'), diff --git a/src/content/dependencies/generateArtTagGalleryPageShowingLine.js b/src/content/dependencies/generateArtTagGalleryPageShowingLine.js index 6df4d0e5..2a34ae57 100644 --- a/src/content/dependencies/generateArtTagGalleryPageShowingLine.js +++ b/src/content/dependencies/generateArtTagGalleryPageShowingLine.js @@ -1,6 +1,4 @@ export default { - extraDependencies: ['html', 'language'], - slots: { showing: { validate: v => v.is('all', 'direct', 'indirect'), diff --git a/src/content/dependencies/generateArtTagInfoPage.js b/src/content/dependencies/generateArtTagInfoPage.js index 9df51b77..683eeab6 100644 --- a/src/content/dependencies/generateArtTagInfoPage.js +++ b/src/content/dependencies/generateArtTagInfoPage.js @@ -1,20 +1,6 @@ import {empty, stitchArrays, unique} from '#sugar'; export default { - contentDependencies: [ - 'generateAdditionalNamesBox', - 'generateArtTagNavLinks', - 'generateArtTagSidebar', - 'generateContentHeading', - 'generatePageLayout', - 'linkArtTagGallery', - 'linkArtTagInfo', - 'linkExternal', - 'transformContent', - ], - - extraDependencies: ['html', 'language', 'wikiData'], - sprawl: ({wikiInfo}) => ({ enableListings: wikiInfo.enableListings, }), @@ -182,12 +168,12 @@ export default { artTagLink: relations.relatedArtTagLinks, annotation: data.relatedArtTagAnnotations, }).map(({artTagLink, annotation}) => - (html.isBlank(annotation) - ? artTagLink - : language.$(capsule, 'tagWithAnnotation', { + (annotation + ? language.$(capsule, 'tagWithAnnotation', { tag: artTagLink, annotation, - })))), + }) + : artTagLink))), }))), html.tag('blockquote', diff --git a/src/content/dependencies/generateArtTagNavLinks.js b/src/content/dependencies/generateArtTagNavLinks.js index 9061a09f..1298ce99 100644 --- a/src/content/dependencies/generateArtTagNavLinks.js +++ b/src/content/dependencies/generateArtTagNavLinks.js @@ -1,12 +1,4 @@ export default { - contentDependencies: [ - 'generateInterpageDotSwitcher', - 'linkArtTagInfo', - 'linkArtTagGallery', - ], - - extraDependencies: ['html', 'language', 'wikiData'], - sprawl: ({wikiInfo}) => ({enableListings: wikiInfo.enableListings}), diff --git a/src/content/dependencies/generateArtTagSidebar.js b/src/content/dependencies/generateArtTagSidebar.js index 9e2f813c..60ea504f 100644 --- a/src/content/dependencies/generateArtTagSidebar.js +++ b/src/content/dependencies/generateArtTagSidebar.js @@ -1,15 +1,6 @@ import {collectTreeLeaves, empty, stitchArrays, unique} from '#sugar'; export default { - contentDependencies: [ - 'generatePageSidebar', - 'generatePageSidebarBox', - 'generateArtTagAncestorDescendantMapList', - 'linkArtTagDynamically', - ], - - extraDependencies: ['html', 'language', 'wikiData'], - sprawl: ({artTagData}) => ({artTagData}), diff --git a/src/content/dependencies/generateArtistArtworkColumn.js b/src/content/dependencies/generateArtistArtworkColumn.js index a4135489..19c66b8a 100644 --- a/src/content/dependencies/generateArtistArtworkColumn.js +++ b/src/content/dependencies/generateArtistArtworkColumn.js @@ -1,6 +1,4 @@ export default { - contentDependencies: ['generateCoverArtwork'], - relations: (relation, artist) => ({ coverArtwork: (artist.hasAvatar diff --git a/src/content/dependencies/generateArtistCredit.js b/src/content/dependencies/generateArtistCredit.js index 6bdbeb23..389de740 100644 --- a/src/content/dependencies/generateArtistCredit.js +++ b/src/content/dependencies/generateArtistCredit.js @@ -1,14 +1,7 @@ -import {compareArrays, empty} from '#sugar'; +import {compareArrays, empty, stitchArrays} from '#sugar'; export default { - contentDependencies: [ - 'generateArtistCreditWikiEditsPart', - 'linkContribution', - ], - - extraDependencies: ['html', 'language'], - - query: (creditContributions, contextContributions) => { + query: (creditContributions, contextContributions, _formatText) => { const query = {}; const featuringFilter = contribution => @@ -36,16 +29,26 @@ export default { // Note that the normal contributions will implicitly *always* // "differ from context" if no context contributions are given, // as in release info lines. - query.normalContributionsDifferFromContext = + + query.normalContributionArtistsDifferFromContext = !compareArrays( query.normalContributions.map(({artist}) => artist), contextNormalContributions.map(({artist}) => artist), - {checkOrder: false}); + {checkOrder: true}); + + query.normalContributionAnnotationsDifferFromContext = + !compareArrays( + query.normalContributions.map(({annotation}) => annotation), + contextNormalContributions.map(({annotation}) => annotation), + {checkOrder: true}); return query; }, - relations: (relation, query, _creditContributions, _contextContributions) => ({ + relations: (relation, query, + _creditContributions, + _contextContributions, + formatText) => ({ normalContributionLinks: query.normalContributions .map(contrib => relation('linkContribution', contrib)), @@ -57,11 +60,25 @@ export default { wikiEditsPart: relation('generateArtistCreditWikiEditsPart', query.wikiEditContributions), + + formatText: + relation('transformContent', formatText), }), - data: (query, _creditContributions, _contextContributions) => ({ - normalContributionsDifferFromContext: - query.normalContributionsDifferFromContext, + data: (query, _creditContributions, _contextContributions, _formatText) => ({ + normalContributionArtistsDifferFromContext: + query.normalContributionArtistsDifferFromContext, + + normalContributionAnnotationsDifferFromContext: + query.normalContributionAnnotationsDifferFromContext, + + normalContributionArtistDirectories: + query.normalContributions + .map(contrib => contrib.artist.directory), + + featuringContributionArtistDirectories: + query.featuringContributions + .map(contrib => contrib.artist.directory), hasWikiEdits: !empty(query.wikiEditContributions), @@ -95,6 +112,10 @@ export default { generate(data, relations, slots, {html, language}) { if (!slots.normalStringKey) return html.blank(); + const effectivelyDiffers = + (slots.showAnnotation && data.normalContributionAnnotationsDifferFromContext) || + (data.normalContributionArtistsDifferFromContext); + for (const link of [ ...relations.normalContributionLinks, ...relations.featuringContributionLinks, @@ -122,59 +143,112 @@ export default { }); } - if (empty(relations.normalContributionLinks)) { - return html.blank(); + let formattedArtistList = null; + + if (!html.isBlank(relations.formatText)) { + formattedArtistList = relations.formatText; + + const substituteContrib = ({link, directory}) => ({ + match: {replacerKey: 'artist', replacerValue: directory}, + substitute: link, + + apply(link, node) { + if (node.data.label) { + link.setSlot('content', language.sanitize(node.data.label)); + } + }, + }); + + relations.formatText.setSlots({ + mode: 'inline', + + substitute: [ + stitchArrays({ + link: relations.normalContributionLinks, + directory: data.normalContributionArtistDirectories, + }).map(substituteContrib), + + stitchArrays({ + link: relations.featuringContributionLinks, + directory: data.featuringContributionArtistDirectories, + }).map(substituteContrib), + ].flat(), + }); } - const artistsList = - (data.hasWikiEdits && slots.showWikiEdits - ? language.$('misc.artistLink.withEditsForWiki', { - artists: - language.formatConjunctionList(relations.normalContributionLinks), - - edits: - relations.wikiEditsPart.slots({ - showAnnotation: slots.showAnnotation, - }), - }) - : language.formatConjunctionList(relations.normalContributionLinks)); - - const featuringList = - language.formatConjunctionList(relations.featuringContributionLinks); - - const everyoneList = - language.formatConjunctionList([ - ...relations.normalContributionLinks, - ...relations.featuringContributionLinks, - ]); - - if (empty(relations.featuringContributionLinks)) { - if (data.normalContributionsDifferFromContext) { - return language.$(slots.normalStringKey, { - ...slots.additionalStringOptions, - artists: artistsList, + let content; + + if (formattedArtistList) { + if (effectivelyDiffers) { + content = + language.$(slots.normalStringKey, { + ...slots.additionalStringOptions, + artists: formattedArtistList, + }); + } + } else { + if (empty(relations.normalContributionLinks)) { + return html.blank(); + } + + const artistsList = + (data.hasWikiEdits && slots.showWikiEdits + ? language.$('misc.artistLink.withEditsForWiki', { + artists: + language.formatConjunctionList(relations.normalContributionLinks), + + edits: + relations.wikiEditsPart.slots({ + showAnnotation: slots.showAnnotation, + }), + }) + + : language.formatConjunctionList(relations.normalContributionLinks)); + + const featuringList = + language.formatConjunctionList(relations.featuringContributionLinks); + + const everyoneList = + language.formatConjunctionList([ + ...relations.normalContributionLinks, + ...relations.featuringContributionLinks, + ]); + + if (empty(relations.featuringContributionLinks)) { + if (effectivelyDiffers) { + content = + language.$(slots.normalStringKey, { + ...slots.additionalStringOptions, + artists: artistsList, + }); + } else { + return html.blank(); + } + } else if (effectivelyDiffers && slots.normalFeaturingStringKey) { + content = + language.$(slots.normalFeaturingStringKey, { + ...slots.additionalStringOptions, + artists: artistsList, + featuring: featuringList, }); + } else if (slots.featuringStringKey) { + content = + language.$(slots.featuringStringKey, { + ...slots.additionalStringOptions, + artists: featuringList, + }); } else { - return html.blank(); + content = + language.$(slots.normalStringKey, { + ...slots.additionalStringOptions, + artists: everyoneList, + }); } } - if (data.normalContributionsDifferFromContext && slots.normalFeaturingStringKey) { - return language.$(slots.normalFeaturingStringKey, { - ...slots.additionalStringOptions, - artists: artistsList, - featuring: featuringList, - }); - } else if (slots.featuringStringKey) { - return language.$(slots.featuringStringKey, { - ...slots.additionalStringOptions, - artists: featuringList, - }); - } else { - return language.$(slots.normalStringKey, { - ...slots.additionalStringOptions, - artists: everyoneList, - }); - } + // TODO: This is obviously evil. + return ( + html.metatag('chunkwrap', {split: /,| (?=and)/}, + html.resolve(content))); }, }; diff --git a/src/content/dependencies/generateArtistCreditWikiEditsPart.js b/src/content/dependencies/generateArtistCreditWikiEditsPart.js index 70296e39..4178928d 100644 --- a/src/content/dependencies/generateArtistCreditWikiEditsPart.js +++ b/src/content/dependencies/generateArtistCreditWikiEditsPart.js @@ -1,12 +1,4 @@ export default { - contentDependencies: [ - 'generateTextWithTooltip', - 'generateTooltip', - 'linkContribution', - ], - - extraDependencies: ['html', 'language'], - relations: (relation, contributions) => ({ textWithTooltip: relation('generateTextWithTooltip'), @@ -48,6 +40,7 @@ export default { showAnnotation: slots.showAnnotation, trimAnnotation: true, preventTooltip: true, + preventWrapping: true, }))), }), }), diff --git a/src/content/dependencies/generateArtistGalleryPage.js b/src/content/dependencies/generateArtistGalleryPage.js index 6a24275e..d8f1c4b1 100644 --- a/src/content/dependencies/generateArtistGalleryPage.js +++ b/src/content/dependencies/generateArtistGalleryPage.js @@ -1,16 +1,6 @@ import {sortArtworksChronologically} from '#sort'; export default { - contentDependencies: [ - 'generateArtistNavLinks', - 'generateCoverGrid', - 'generatePageLayout', - 'image', - 'linkAnythingMan', - ], - - extraDependencies: ['html', 'language'], - query: (artist) => ({ artworks: sortArtworksChronologically( @@ -58,6 +48,10 @@ export default { .map(artwork => artwork.artistContribs .filter(contrib => contrib.artist !== artist) .map(contrib => contrib.artist.name)), + + allWarnings: + query.artworks + .flatMap(artwork => artwork.contentWarnings), }), generate: (data, relations, {html, language}) => @@ -93,6 +87,8 @@ export default { artists: language.formatUnitList(names), })), + + revealAllWarnings: data.allWarnings, }), ], diff --git a/src/content/dependencies/generateArtistGroupContributionsInfo.js b/src/content/dependencies/generateArtistGroupContributionsInfo.js index 3e0cd1d2..6940053f 100644 --- a/src/content/dependencies/generateArtistGroupContributionsInfo.js +++ b/src/content/dependencies/generateArtistGroupContributionsInfo.js @@ -1,83 +1,87 @@ -import {empty, filterProperties, stitchArrays, unique} from '#sugar'; +import {accumulateSum, empty, stitchArrays, withEntries} from '#sugar'; export default { - contentDependencies: ['linkGroup'], - extraDependencies: ['html', 'language', 'wikiData'], + sprawl: ({groupCategoryData}) => ({ + groupOrder: + groupCategoryData.flatMap(category => category.groups), + }), - sprawl({groupCategoryData}) { - return { - groupOrder: groupCategoryData.flatMap(category => category.groups), - } - }, + query(sprawl, contributions) { + const allGroupsUnordered = + new Set(contributions.flatMap(contrib => contrib.groups)); - query(sprawl, tracksAndAlbums) { - const filteredAlbums = tracksAndAlbums.filter(thing => !thing.album); - const filteredTracks = tracksAndAlbums.filter(thing => thing.album); + const allGroupsOrdered = + sprawl.groupOrder.filter(group => allGroupsUnordered.has(group)); - const allAlbums = unique([ - ...filteredAlbums, - ...filteredTracks.map(track => track.album), - ]); + const groupToThingsCountedForContributions = + new Map(allGroupsOrdered.map(group => [group, new Set])); - const allGroupsUnordered = new Set(Array.from(allAlbums).flatMap(album => album.groups)); - const allGroupsOrdered = sprawl.groupOrder.filter(group => allGroupsUnordered.has(group)); + const groupToThingsCountedForDuration = + new Map(allGroupsOrdered.map(group => [group, new Set])); - const mapTemplate = allGroupsOrdered.map(group => [group, 0]); - const groupToCountMap = new Map(mapTemplate); - const groupToDurationMap = new Map(mapTemplate); - const groupToDurationCountMap = new Map(mapTemplate); - - for (const album of filteredAlbums) { - for (const group of album.groups) { - groupToCountMap.set(group, groupToCountMap.get(group) + 1); - } - } + for (const contrib of contributions) { + for (const group of contrib.groups) { + if (contrib.countInContributionTotals) { + groupToThingsCountedForContributions.get(group).add(contrib.thing); + } - for (const track of filteredTracks) { - for (const group of track.album.groups) { - groupToCountMap.set(group, groupToCountMap.get(group) + 1); - if (track.duration && track.mainReleaseTrack === null) { - groupToDurationMap.set(group, groupToDurationMap.get(group) + track.duration); - groupToDurationCountMap.set(group, groupToDurationCountMap.get(group) + 1); + if (contrib.countInDurationTotals) { + groupToThingsCountedForDuration.get(group).add(contrib.thing); } } } + const groupToTotalContributions = + withEntries( + groupToThingsCountedForContributions, + entries => entries.map( + ([group, things]) => + ([group, things.size]))); + + const groupToTotalDuration = + withEntries( + groupToThingsCountedForDuration, + entries => entries.map( + ([group, things]) => + ([group, accumulateSum(things, thing => thing.duration)]))) + const groupsSortedByCount = allGroupsOrdered - .slice() - .sort((a, b) => groupToCountMap.get(b) - groupToCountMap.get(a)); + .filter(group => groupToTotalContributions.get(group) > 0) + .sort((a, b) => + (groupToTotalContributions.get(b) + - groupToTotalContributions.get(a))); - // The filter here ensures all displayed groups have at least some duration - // when sorting by duration. const groupsSortedByDuration = allGroupsOrdered - .filter(group => groupToDurationMap.get(group) > 0) - .sort((a, b) => groupToDurationMap.get(b) - groupToDurationMap.get(a)); + .filter(group => groupToTotalDuration.get(group) > 0) + .sort((a, b) => + (groupToTotalDuration.get(b) + - groupToTotalDuration.get(a))); const groupCountsSortedByCount = groupsSortedByCount - .map(group => groupToCountMap.get(group)); + .map(group => groupToTotalContributions.get(group)); const groupDurationsSortedByCount = groupsSortedByCount - .map(group => groupToDurationMap.get(group)); + .map(group => groupToTotalDuration.get(group)); const groupDurationsApproximateSortedByCount = groupsSortedByCount - .map(group => groupToDurationCountMap.get(group) > 1); + .map(group => groupToThingsCountedForDuration.get(group).size > 1); const groupCountsSortedByDuration = groupsSortedByDuration - .map(group => groupToCountMap.get(group)); + .map(group => groupToTotalContributions.get(group)); const groupDurationsSortedByDuration = groupsSortedByDuration - .map(group => groupToDurationMap.get(group)); + .map(group => groupToTotalDuration.get(group)); const groupDurationsApproximateSortedByDuration = groupsSortedByDuration - .map(group => groupToDurationCountMap.get(group) > 1); + .map(group => groupToThingsCountedForDuration.get(group).size > 1); return { groupsSortedByCount, @@ -93,29 +97,35 @@ export default { }; }, - relations(relation, query) { - return { - groupLinksSortedByCount: - query.groupsSortedByCount - .map(group => relation('linkGroup', group)), + relations: (relation, query) => ({ + groupLinksSortedByCount: + query.groupsSortedByCount + .map(group => relation('linkGroup', group)), - groupLinksSortedByDuration: - query.groupsSortedByDuration - .map(group => relation('linkGroup', group)), - }; - }, + groupLinksSortedByDuration: + query.groupsSortedByDuration + .map(group => relation('linkGroup', group)), + }), - data(query) { - return filterProperties(query, [ - 'groupCountsSortedByCount', - 'groupDurationsSortedByCount', - 'groupDurationsApproximateSortedByCount', + data: (query) => ({ + groupCountsSortedByCount: + query.groupCountsSortedByCount, - 'groupCountsSortedByDuration', - 'groupDurationsSortedByDuration', - 'groupDurationsApproximateSortedByDuration', - ]); - }, + groupDurationsSortedByCount: + query.groupDurationsSortedByCount, + + groupDurationsApproximateSortedByCount: + query.groupDurationsApproximateSortedByCount, + + groupCountsSortedByDuration: + query.groupCountsSortedByDuration, + + groupDurationsSortedByDuration: + query.groupDurationsSortedByDuration, + + groupDurationsApproximateSortedByDuration: + query.groupDurationsApproximateSortedByDuration, + }), slots: { title: { diff --git a/src/content/dependencies/generateArtistInfoPage.js b/src/content/dependencies/generateArtistInfoPage.js index 3a3cf8b7..cf8ce994 100644 --- a/src/content/dependencies/generateArtistInfoPage.js +++ b/src/content/dependencies/generateArtistInfoPage.js @@ -1,48 +1,18 @@ import {empty, stitchArrays, unique} from '#sugar'; export default { - contentDependencies: [ - 'generateArtistArtworkColumn', - 'generateArtistGroupContributionsInfo', - 'generateArtistInfoPageArtworksChunkedList', - 'generateArtistInfoPageCommentaryChunkedList', - 'generateArtistInfoPageFlashesChunkedList', - 'generateArtistInfoPageTracksChunkedList', - 'generateArtistNavLinks', - 'generateContentHeading', - 'generatePageLayout', - 'linkArtistGallery', - 'linkExternal', - 'linkGroup', - 'transformContent', - ], - - extraDependencies: ['html', 'language'], - query: (artist) => ({ - // Even if an artist has served as both "artist" (compositional) and - // "contributor" (instruments, production, etc) on the same track, that - // track only counts as one unique contribution in the list. - allTracks: - unique( - ([ - artist.trackArtistContributions, - artist.trackContributorContributions, - ]).flat() - .map(({thing}) => thing)), - - // Artworks are different, though. We intentionally duplicate album data - // objects when the artist has contributed some combination of cover art, - // wallpaper, and banner - these each count as a unique contribution. - allArtworkThings: - ([ - artist.albumCoverArtistContributions, - artist.albumWallpaperArtistContributions, - artist.albumBannerArtistContributions, - artist.trackCoverArtistContributions, - ]).flat() - .filter(({annotation}) => !annotation?.startsWith('edits for wiki')) - .map(({thing}) => thing.thing), + trackContributions: [ + ...artist.trackArtistContributions, + ...artist.trackContributorContributions, + ], + + artworkContributions: [ + ...artist.albumCoverArtistContributions, + ...artist.albumWallpaperArtistContributions, + ...artist.albumBannerArtistContributions, + ...artist.trackCoverArtistContributions, + ], // Banners and wallpapers don't show up in the artist gallery page, only // cover art. @@ -93,7 +63,7 @@ export default { relation('generateArtistInfoPageTracksChunkedList', artist), tracksGroupInfo: - relation('generateArtistGroupContributionsInfo', query.allTracks), + relation('generateArtistGroupContributionsInfo', query.trackContributions), artworksChunkedList: relation('generateArtistInfoPageArtworksChunkedList', artist, false), @@ -102,7 +72,7 @@ export default { relation('generateArtistInfoPageArtworksChunkedList', artist, true), artworksGroupInfo: - relation('generateArtistGroupContributionsInfo', query.allArtworkThings), + relation('generateArtistGroupContributionsInfo', query.artworkContributions), artistGalleryLink: (query.hasGallery @@ -128,7 +98,11 @@ export default { .map(({annotation}) => annotation), totalTrackCount: - query.allTracks.length, + unique( + query.trackContributions + .filter(contrib => contrib.countInContributionTotals) + .map(contrib => contrib.thing)) + .length, totalDuration: artist.totalDuration, diff --git a/src/content/dependencies/generateArtistInfoPageArtworksChunk.js b/src/content/dependencies/generateArtistInfoPageArtworksChunk.js index 66e4204a..f4c9439a 100644 --- a/src/content/dependencies/generateArtistInfoPageArtworksChunk.js +++ b/src/content/dependencies/generateArtistInfoPageArtworksChunk.js @@ -1,12 +1,4 @@ export default { - contentDependencies: [ - 'generateArtistInfoPageChunk', - 'generateArtistInfoPageArtworksChunkItem', - 'linkAlbum', - ], - - extraDependencies: ['html'], - relations: (relation, album, contribs) => ({ template: relation('generateArtistInfoPageChunk'), diff --git a/src/content/dependencies/generateArtistInfoPageArtworksChunkItem.js b/src/content/dependencies/generateArtistInfoPageArtworksChunkItem.js index 2f2fe0c5..e3ba5342 100644 --- a/src/content/dependencies/generateArtistInfoPageArtworksChunkItem.js +++ b/src/content/dependencies/generateArtistInfoPageArtworksChunkItem.js @@ -1,19 +1,13 @@ -export default { - contentDependencies: [ - 'generateArtistInfoPageChunkItem', - 'generateArtistInfoPageOtherArtistLinks', - 'linkTrack', - ], - - extraDependencies: ['html', 'language'], +import {empty} from '#sugar'; +export default { query: (contrib) => ({ kind: - (contrib.isBannerArtistContribution + (contrib.thing.thingProperty === 'bannerArtwork' ? 'banner' - : contrib.isWallpaperArtistContribution + : contrib.thing.thingProperty === 'wallpaperArtwork' ? 'wallpaper' - : contrib.isForAlbum + : contrib.thing.thingProperty === 'coverArtworks' ? 'album-cover' : 'track-cover'), }), @@ -29,6 +23,9 @@ export default { otherArtistLinks: relation('generateArtistInfoPageOtherArtistLinks', [contrib]), + + originDetails: + relation('transformContent', contrib.thing.originDetails), }), data: (query, contrib) => ({ @@ -37,6 +34,9 @@ export default { annotation: contrib.annotation, + + label: + contrib.thing.label, }), slots: { @@ -51,9 +51,33 @@ export default { otherArtistLinks: relations.otherArtistLinks, annotation: - (slots.filterEditsForWiki - ? data.annotation?.replace(/^edits for wiki(: )?/, '') - : data.annotation), + language.encapsulate('artistPage.creditList.entry.artwork.accent', workingCapsule => { + const workingOptions = {}; + + const artworkLabel = data.label; + + if (artworkLabel) { + workingCapsule += '.withLabel'; + workingOptions.label = + language.typicallyLowerCase(artworkLabel); + } + + const contribAnnotation = + (slots.filterEditsForWiki + ? data.annotation?.replace(/^edits for wiki(: )?/, '') + : data.annotation); + + if (contribAnnotation) { + workingCapsule += '.withAnnotation'; + workingOptions.annotation = contribAnnotation; + } + + if (empty(Object.keys(workingOptions))) { + return html.blank(); + } + + return language.$(workingCapsule, workingOptions); + }), content: language.encapsulate('artistPage.creditList.entry', capsule => @@ -68,5 +92,11 @@ export default { : data.kind === 'banner' ? language.$(capsule, 'bannerArt') : language.$(capsule, 'coverArt')))))), + + originDetails: + relations.originDetails.slots({ + mode: 'inline', + absorbPunctuationFollowingExternalLinks: false, + }), }), }; diff --git a/src/content/dependencies/generateArtistInfoPageArtworksChunkedList.js b/src/content/dependencies/generateArtistInfoPageArtworksChunkedList.js index 75a4aa5a..40ffc5dd 100644 --- a/src/content/dependencies/generateArtistInfoPageArtworksChunkedList.js +++ b/src/content/dependencies/generateArtistInfoPageArtworksChunkedList.js @@ -3,11 +3,6 @@ import {sortAlbumsTracksChronologically, sortContributionsChronologically} import {chunkByConditions, stitchArrays} from '#sugar'; export default { - contentDependencies: [ - 'generateArtistInfoPageChunkedList', - 'generateArtistInfoPageArtworksChunk', - ], - query(artist, filterEditsForWiki) { const query = {}; diff --git a/src/content/dependencies/generateArtistInfoPageChunk.js b/src/content/dependencies/generateArtistInfoPageChunk.js index fce68a7d..80429912 100644 --- a/src/content/dependencies/generateArtistInfoPageChunk.js +++ b/src/content/dependencies/generateArtistInfoPageChunk.js @@ -1,8 +1,6 @@ import {empty} from '#sugar'; export default { - extraDependencies: ['html', 'language'], - slots: { mode: { validate: v => v.is('flash', 'album'), diff --git a/src/content/dependencies/generateArtistInfoPageChunkItem.js b/src/content/dependencies/generateArtistInfoPageChunkItem.js index 7987b642..8117ca9a 100644 --- a/src/content/dependencies/generateArtistInfoPageChunkItem.js +++ b/src/content/dependencies/generateArtistInfoPageChunkItem.js @@ -1,9 +1,6 @@ import {empty} from '#sugar'; export default { - contentDependencies: ['generateTextWithTooltip'], - extraDependencies: ['html', 'language'], - relations: (relation) => ({ textWithTooltip: relation('generateTextWithTooltip'), @@ -33,6 +30,11 @@ export default { type: 'html', mutable: false, }, + + originDetails: { + type: 'html', + mutable: false, + }, }, generate: (relations, slots, {html, language}) => @@ -40,52 +42,59 @@ export default { html.tag('li', slots.rerelease && {class: 'rerelease'}, - language.encapsulate(entryCapsule, workingCapsule => { - const workingOptions = {entry: slots.content}; - - if (!html.isBlank(slots.rereleaseTooltip)) { - workingCapsule += '.rerelease'; - workingOptions.rerelease = - relations.textWithTooltip.slots({ - attributes: {class: 'rerelease'}, - text: language.$(entryCapsule, 'rerelease.term'), - tooltip: slots.rereleaseTooltip, - }); - - return language.$(workingCapsule, workingOptions); - } - - if (!html.isBlank(slots.firstReleaseTooltip)) { - workingCapsule += '.firstRelease'; - workingOptions.firstRelease = - relations.textWithTooltip.slots({ - attributes: {class: 'first-release'}, - text: language.$(entryCapsule, 'firstRelease.term'), - tooltip: slots.firstReleaseTooltip, - }); - - return language.$(workingCapsule, workingOptions); - } - - let anyAccent = false; - - if (!empty(slots.otherArtistLinks)) { - anyAccent = true; - workingCapsule += '.withArtists'; - workingOptions.artists = - language.formatConjunctionList(slots.otherArtistLinks); - } - - if (!html.isBlank(slots.annotation)) { - anyAccent = true; - workingCapsule += '.withAnnotation'; - workingOptions.annotation = slots.annotation; - } - - if (anyAccent) { - return language.$(workingCapsule, workingOptions); - } else { - return slots.content; - } - }))), + html.tags([ + language.encapsulate(entryCapsule, workingCapsule => { + const workingOptions = {entry: slots.content}; + + if (!html.isBlank(slots.rereleaseTooltip)) { + workingCapsule += '.rerelease'; + workingOptions.rerelease = + relations.textWithTooltip.slots({ + attributes: {class: 'rerelease'}, + text: language.$(entryCapsule, 'rerelease.term'), + tooltip: slots.rereleaseTooltip, + }); + + return language.$(workingCapsule, workingOptions); + } + + if (!html.isBlank(slots.firstReleaseTooltip)) { + workingCapsule += '.firstRelease'; + workingOptions.firstRelease = + relations.textWithTooltip.slots({ + attributes: {class: 'first-release'}, + text: language.$(entryCapsule, 'firstRelease.term'), + tooltip: slots.firstReleaseTooltip, + }); + + return language.$(workingCapsule, workingOptions); + } + + let anyAccent = false; + + if (!empty(slots.otherArtistLinks)) { + anyAccent = true; + workingCapsule += '.withArtists'; + workingOptions.artists = + language.formatConjunctionList(slots.otherArtistLinks); + } + + if (!html.isBlank(slots.annotation)) { + anyAccent = true; + workingCapsule += '.withAnnotation'; + workingOptions.annotation = slots.annotation; + } + + if (anyAccent) { + return language.$(workingCapsule, workingOptions); + } else { + return slots.content; + } + }), + + html.tag('span', {class: 'origin-details'}, + {[html.onlyIfContent]: true}, + + slots.originDetails), + ]))), }; diff --git a/src/content/dependencies/generateArtistInfoPageChunkedList.js b/src/content/dependencies/generateArtistInfoPageChunkedList.js index e7915ab7..54577885 100644 --- a/src/content/dependencies/generateArtistInfoPageChunkedList.js +++ b/src/content/dependencies/generateArtistInfoPageChunkedList.js @@ -1,6 +1,4 @@ export default { - extraDependencies: ['html'], - slots: { groupInfo: { type: 'html', diff --git a/src/content/dependencies/generateArtistInfoPageCommentaryChunkedList.js b/src/content/dependencies/generateArtistInfoPageCommentaryChunkedList.js index d0c5e14e..caec58d6 100644 --- a/src/content/dependencies/generateArtistInfoPageCommentaryChunkedList.js +++ b/src/content/dependencies/generateArtistInfoPageCommentaryChunkedList.js @@ -7,18 +7,6 @@ import { } from '#sort'; export default { - contentDependencies: [ - 'generateArtistInfoPageChunk', - 'generateArtistInfoPageChunkItem', - 'linkAlbum', - 'linkFlash', - 'linkFlashAct', - 'linkTrack', - 'transformContent', - ], - - extraDependencies: ['html', 'language'], - query(artist, filterWikiEditorCommentary) { const processEntry = ({ thing, @@ -43,6 +31,7 @@ export default { flash, annotation: entry.annotation, + annotationParts: entry.annotationParts, }, }); @@ -88,10 +77,10 @@ export default { thing.commentary .filter(entry => entry.artists.includes(artist)) - .filter(({annotation}) => + .filter(entry => (filterWikiEditorCommentary - ? annotation?.match(/^wiki editor/i) - : !annotation?.match(/^wiki editor/i))) + ? entry.isWikiEditorCommentary + : !entry.isWikiEditorCommentary)) .map(entry => processEntry({thing, entry}))); @@ -184,11 +173,13 @@ export default { itemAnnotations: query.chunks .map(({chunk}) => chunk - .map(({annotation}) => + .map(entry => relation('transformContent', (filterWikiEditorCommentary - ? annotation?.replace(/^wiki editor(, )?/i, '') - : annotation)))), + ? entry.annotationParts + .filter(part => part !== 'wiki editor') + .join(', ') + : entry.annotation)))), }), data: (query, _artist, _filterWikiEditorCommentary) => ({ diff --git a/src/content/dependencies/generateArtistInfoPageFirstReleaseTooltip.js b/src/content/dependencies/generateArtistInfoPageFirstReleaseTooltip.js index f86dead7..eb32cebf 100644 --- a/src/content/dependencies/generateArtistInfoPageFirstReleaseTooltip.js +++ b/src/content/dependencies/generateArtistInfoPageFirstReleaseTooltip.js @@ -1,18 +1,10 @@ -import {sortChronologically} from '#sort'; +import {sortAlbumsTracksChronologically} from '#sort'; import {stitchArrays} from '#sugar'; export default { - contentDependencies: [ - 'generateColorStyleAttribute', - 'generateTooltip', - 'linkOtherReleaseOnArtistInfoPage', - ], - - extraDependencies: ['html', 'language'], - query: (track) => ({ rereleases: - sortChronologically(track.allReleases).slice(1), + sortAlbumsTracksChronologically(track.allReleases).slice(1), }), relations: (relation, query, track, artist) => ({ diff --git a/src/content/dependencies/generateArtistInfoPageFlashesChunk.js b/src/content/dependencies/generateArtistInfoPageFlashesChunk.js index 8aa7223a..acdb9897 100644 --- a/src/content/dependencies/generateArtistInfoPageFlashesChunk.js +++ b/src/content/dependencies/generateArtistInfoPageFlashesChunk.js @@ -1,10 +1,4 @@ export default { - contentDependencies: [ - 'generateArtistInfoPageChunk', - 'generateArtistInfoPageFlashesChunkItem', - 'linkFlashAct', - ], - relations: (relation, flashAct, contribs) => ({ template: relation('generateArtistInfoPageChunk'), diff --git a/src/content/dependencies/generateArtistInfoPageFlashesChunkItem.js b/src/content/dependencies/generateArtistInfoPageFlashesChunkItem.js index e4908bf9..36d7945d 100644 --- a/src/content/dependencies/generateArtistInfoPageFlashesChunkItem.js +++ b/src/content/dependencies/generateArtistInfoPageFlashesChunkItem.js @@ -1,8 +1,4 @@ export default { - contentDependencies: ['generateArtistInfoPageChunkItem', 'linkFlash'], - - extraDependencies: ['language'], - relations: (relation, contrib) => ({ // Flashes and games can list multiple contributors as collaborative // credits, but we don't display these on the artist page, since they diff --git a/src/content/dependencies/generateArtistInfoPageFlashesChunkedList.js b/src/content/dependencies/generateArtistInfoPageFlashesChunkedList.js index b347faf5..762386a2 100644 --- a/src/content/dependencies/generateArtistInfoPageFlashesChunkedList.js +++ b/src/content/dependencies/generateArtistInfoPageFlashesChunkedList.js @@ -3,13 +3,6 @@ import {sortContributionsChronologically, sortFlashesChronologically} import {chunkByConditions, stitchArrays} from '#sugar'; export default { - contentDependencies: [ - 'generateArtistInfoPageChunkedList', - 'generateArtistInfoPageFlashesChunk', - ], - - extraDependencies: ['wikiData'], - sprawl: ({wikiInfo}) => ({ enableFlashesAndGames: wikiInfo.enableFlashesAndGames, diff --git a/src/content/dependencies/generateArtistInfoPageOtherArtistLinks.js b/src/content/dependencies/generateArtistInfoPageOtherArtistLinks.js index dcee9c00..afb61c33 100644 --- a/src/content/dependencies/generateArtistInfoPageOtherArtistLinks.js +++ b/src/content/dependencies/generateArtistInfoPageOtherArtistLinks.js @@ -1,8 +1,6 @@ import {unique} from '#sugar'; export default { - contentDependencies: ['linkArtist'], - query(contribs) { const associatedContributionsByOtherArtists = contribs diff --git a/src/content/dependencies/generateArtistInfoPageRereleaseTooltip.js b/src/content/dependencies/generateArtistInfoPageRereleaseTooltip.js index 1d849919..70bada19 100644 --- a/src/content/dependencies/generateArtistInfoPageRereleaseTooltip.js +++ b/src/content/dependencies/generateArtistInfoPageRereleaseTooltip.js @@ -1,17 +1,9 @@ -import {sortChronologically} from '#sort'; +import {sortAlbumsTracksChronologically} from '#sort'; export default { - contentDependencies: [ - 'generateColorStyleAttribute', - 'generateTooltip', - 'linkOtherReleaseOnArtistInfoPage' - ], - - extraDependencies: ['html', 'language'], - query: (track) => ({ firstRelease: - sortChronologically(track.allReleases)[0], + sortAlbumsTracksChronologically(track.allReleases)[0], }), relations: (relation, query, track, artist) => ({ diff --git a/src/content/dependencies/generateArtistInfoPageTracksChunk.js b/src/content/dependencies/generateArtistInfoPageTracksChunk.js index f6d70901..3e4cc4e9 100644 --- a/src/content/dependencies/generateArtistInfoPageTracksChunk.js +++ b/src/content/dependencies/generateArtistInfoPageTracksChunk.js @@ -2,12 +2,6 @@ import {unique} from '#sugar'; import {getTotalDuration} from '#wiki-data'; export default { - contentDependencies: [ - 'generateArtistInfoPageChunk', - 'generateArtistInfoPageTracksChunkItem', - 'linkAlbum', - ], - relations: (relation, artist, album, trackContribLists) => ({ template: relation('generateArtistInfoPageChunk'), diff --git a/src/content/dependencies/generateArtistInfoPageTracksChunkItem.js b/src/content/dependencies/generateArtistInfoPageTracksChunkItem.js index a42d6fee..895cc0d8 100644 --- a/src/content/dependencies/generateArtistInfoPageTracksChunkItem.js +++ b/src/content/dependencies/generateArtistInfoPageTracksChunkItem.js @@ -1,18 +1,8 @@ -import {sortChronologically} from '#sort'; +import {sortAlbumsTracksChronologically} from '#sort'; import {empty} from '#sugar'; export default { - contentDependencies: [ - 'generateArtistInfoPageChunkItem', - 'generateArtistInfoPageFirstReleaseTooltip', - 'generateArtistInfoPageOtherArtistLinks', - 'generateArtistInfoPageRereleaseTooltip', - 'linkTrack', - ], - - extraDependencies: ['html', 'language'], - - query (_artist, contribs) { + query(_artist, contribs) { const query = {}; // TODO: Very mysterious what to do if the set of contributions is, @@ -22,11 +12,11 @@ export default { const creditedAsArtist = contribs - .some(contrib => contrib.isArtistContribution); + .some(contrib => contrib.thingProperty === 'artistContribs'); const creditedAsContributor = contribs - .some(contrib => contrib.isContributorContribution); + .some(contrib => contrib.thingProperty === 'contributorContribs'); const annotatedContribs = contribs @@ -34,11 +24,11 @@ export default { const annotatedArtistContribs = annotatedContribs - .filter(contrib => contrib.isArtistContribution); + .filter(contrib => contrib.thingProperty === 'artistContribs'); const annotatedContributorContribs = annotatedContribs - .filter(contrib => contrib.isContributorContribution); + .filter(contrib => contrib.thingProperty === 'contributorContribs'); // Don't display annotations associated with crediting in the // Contributors field if the artist is also credited as an Artist @@ -73,7 +63,7 @@ export default { // different - and it's the latter that determines whether the // track is a rerelease! const allReleasesChronologically = - sortChronologically(query.track.allReleases); + sortAlbumsTracksChronologically(query.track.allReleases); query.isFirstRelease = allReleasesChronologically[0] === query.track; diff --git a/src/content/dependencies/generateArtistInfoPageTracksChunkedList.js b/src/content/dependencies/generateArtistInfoPageTracksChunkedList.js index 84eb29ac..15588ed3 100644 --- a/src/content/dependencies/generateArtistInfoPageTracksChunkedList.js +++ b/src/content/dependencies/generateArtistInfoPageTracksChunkedList.js @@ -4,11 +4,6 @@ import {stitchArrays} from '#sugar'; import {chunkArtistTrackContributions} from '#wiki-data'; export default { - contentDependencies: [ - 'generateArtistInfoPageChunkedList', - 'generateArtistInfoPageTracksChunk', - ], - query(artist) { const query = {}; diff --git a/src/content/dependencies/generateArtistNavLinks.js b/src/content/dependencies/generateArtistNavLinks.js index 1b4b6eca..69ae3e19 100644 --- a/src/content/dependencies/generateArtistNavLinks.js +++ b/src/content/dependencies/generateArtistNavLinks.js @@ -1,14 +1,6 @@ import {empty} from '#sugar'; export default { - contentDependencies: [ - 'generateInterpageDotSwitcher', - 'linkArtist', - 'linkArtistGallery', - ], - - extraDependencies: ['html', 'language', 'wikiData'], - sprawl: ({wikiInfo}) => ({ enableListings: wikiInfo.enableListings, @@ -34,6 +26,9 @@ export default { (query.hasGallery ? relation('linkArtistGallery', artist) : null), + + artistRollingWindowLink: + relation('linkArtistRollingWindow', artist), }), data: (_query, sprawl) => ({ @@ -45,7 +40,7 @@ export default { showExtraLinks: {type: 'boolean', default: false}, currentExtra: { - validate: v => v.is('gallery'), + validate: v => v.is('gallery', 'rolling-window'), }, }, @@ -79,6 +74,7 @@ export default { }), slots.showExtraLinks && + slots.currentExtra !== 'rolling-window' && relations.artistGalleryLink?.slots({ attributes: [ slots.currentExtra === 'gallery' && @@ -87,6 +83,12 @@ export default { content: language.$('misc.nav.gallery'), }), + + slots.currentExtra === 'rolling-window' && + relations.artistRollingWindowLink.slots({ + attributes: {class: 'current'}, + content: language.$('misc.nav.rollingWindow'), + }), ], }), }, diff --git a/src/content/dependencies/generateArtistRollingWindowPage.js b/src/content/dependencies/generateArtistRollingWindowPage.js new file mode 100644 index 00000000..aafd1b55 --- /dev/null +++ b/src/content/dependencies/generateArtistRollingWindowPage.js @@ -0,0 +1,418 @@ +import {sortAlbumsTracksChronologically} from '#sort'; +import Thing from '#thing'; + +import { + chunkByConditions, + filterMultipleArrays, + empty, + sortMultipleArrays, + stitchArrays, + unique, +} from '#sugar'; + +export default { + sprawl: ({groupCategoryData}) => ({ + groupCategoryData, + }), + + query(sprawl, artist) { + const query = {}; + + const musicContributions = + artist.musicContributions + .filter(contrib => contrib.date); + + const artworkContributions = + artist.artworkContributions + .filter(contrib => + contrib.date && + contrib.thingProperty !== 'wallpaperArtistContribs' && + contrib.thingProperty !== 'bannerArtistContribs'); + + const musicThings = + musicContributions + .map(contrib => contrib.thing); + + const artworkThings = + artworkContributions + .map(contrib => contrib.thing.thing); + + const musicContributionDates = + musicContributions + .map(contrib => contrib.date); + + const artworkContributionDates = + artworkContributions + .map(contrib => contrib.date); + + const musicContributionKinds = + musicContributions + .map(() => 'music'); + + const artworkContributionKinds = + artworkContributions + .map(() => 'artwork'); + + const allThings = [ + ...artworkThings, + ...musicThings, + ]; + + const allContributionDates = [ + ...artworkContributionDates, + ...musicContributionDates, + ]; + + const allContributionKinds = [ + ...artworkContributionKinds, + ...musicContributionKinds, + ]; + + const sortedThings = + sortAlbumsTracksChronologically(allThings.slice(), {latestFirst: true}); + + sortMultipleArrays( + allThings, + allContributionDates, + allContributionKinds, + (thing1, thing2) => + sortedThings.indexOf(thing1) - + sortedThings.indexOf(thing2)); + + const sourceIndices = + Array.from({length: allThings.length}, (_, i) => i); + + const sourceChunks = + chunkByConditions(sourceIndices, [ + (index1, index2) => + allThings[index1] !== + allThings[index2], + ]); + + const indicesTo = array => index => array[index]; + + query.things = + sourceChunks + .map(chunks => allThings[chunks[0]]); + + query.thingGroups = + query.things.map(thing => + (thing.constructor[Thing.referenceType] === 'album' + ? thing.groups + : thing.constructor[Thing.referenceType] === 'track' + ? thing.album.groups + : null)); + + query.thingContributionDates = + sourceChunks + .map(indices => indices + .map(indicesTo(allContributionDates))); + + query.thingContributionKinds = + sourceChunks + .map(indices => indices + .map(indicesTo(allContributionKinds))); + + // Matches the "kind" dropdown. + const kinds = ['artwork', 'music', 'flash']; + + const allKinds = + unique(query.thingContributionKinds.flat(2)); + + query.kinds = + kinds + .filter(kind => allKinds.includes(kind)); + + query.firstKind = + query.kinds.at(0); + + query.thingArtworks = + stitchArrays({ + thing: query.things, + kinds: query.thingContributionKinds, + }).map(({thing, kinds}) => + (kinds.includes('artwork') + ? (thing.coverArtworks ?? thing.trackArtworks ?? []) + .find(artwork => artwork.artistContribs + .some(contrib => contrib.artist === artist)) + : (thing.coverArtworks ?? thing.trackArtworks)?.[0] ?? + thing.album?.coverArtworks[0] ?? + null)); + + const allGroups = + unique(query.thingGroups.flat()); + + query.groupCategories = + sprawl.groupCategoryData.slice(); + + query.groupCategoryGroups = + sprawl.groupCategoryData + .map(category => category.groups + .filter(group => allGroups.includes(group))); + + filterMultipleArrays( + query.groupCategories, + query.groupCategoryGroups, + (_category, groups) => !empty(groups)); + + const groupsMatchingFirstKind = + unique( + stitchArrays({ + thing: query.things, + groups: query.thingGroups, + kinds: query.thingContributionKinds, + }).filter(({kinds}) => kinds.includes(query.firstKind)) + .flatMap(({groups}) => groups)); + + query.firstGroup = + sprawl.groupCategoryData + .flatMap(category => category.groups) + .find(group => groupsMatchingFirstKind.includes(group)); + + query.firstGroupCategory = + query.firstGroup.category; + + return query; + }, + + relations: (relation, query, sprawl, artist) => ({ + layout: + relation('generatePageLayout'), + + artistNavLinks: + relation('generateArtistNavLinks', artist), + + sourceGrid: + relation('generateCoverGrid'), + + sourceGridImages: + query.thingArtworks + .map(artwork => relation('image', artwork)), + + sourceGridLinks: + query.things + .map(thing => relation('linkAnythingMan', thing)), + }), + + data: (query, sprawl, artist) => ({ + name: + artist.name, + + categoryGroupDirectories: + query.groupCategoryGroups + .map(groups => groups + .map(group => group.directory)), + + categoryGroupNames: + query.groupCategoryGroups + .map(groups => groups + .map(group => group.name)), + + firstGroupCategoryIndex: + query.groupCategories + .indexOf(query.firstGroupCategory), + + firstGroupIndex: + stitchArrays({ + category: query.groupCategories, + groups: query.groupCategoryGroups, + }).find(({category}) => category === query.firstGroupCategory) + .groups + .indexOf(query.firstGroup), + + kinds: + query.kinds, + + sourceGridNames: + query.things + .map(thing => thing.name), + + sourceGridGroupDirectories: + query.thingGroups + .map(groups => groups + .map(group => group.directory)), + + sourceGridGroupNames: + query.thingGroups + .map(groups => groups + .map(group => group.name)), + + sourceGridContributionKinds: + query.thingContributionKinds, + + sourceGridContributionDates: + query.thingContributionDates, + }), + + generate: (data, relations, {html, language}) => + relations.layout.slots({ + title: + language.$('artistRollingWindowPage.title', { + artist: data.name, + }), + + mainClasses: ['top-index'], + mainContent: [ + html.tag('p', {id: 'timeframe-configuration'}, + language.$('artistRollingWindowPage.windowConfigurationLine', { + timeBefore: + language.$('artistRollingWindowPage.timeframe.months', { + input: + html.tag('input', {id: 'timeframe-months-before'}, + {type: 'number'}, + {value: 3, min: 0}), + }), + + timeAfter: + language.$('artistRollingWindowPage.timeframe.months', { + input: + html.tag('input', {id: 'timeframe-months-after'}, + {type: 'number'}, + {value: 3, min: 1}), + }), + + peek: + language.$('artistRollingWindowPage.timeframe.months', { + input: + html.tag('input', {id: 'timeframe-months-peek'}, + {type: 'number'}, + {value: 1, min: 0}), + }), + })), + + html.tag('p', {id: 'contribution-configuration'}, + language.$('artistRollingWindowPage.contributionConfigurationLine', { + kind: + html.tag('select', {id: 'contribution-kind'}, + data.kinds.map(kind => + html.tag('option', {value: kind}, + language.$('artistRollingWindowPage.contributionKind', kind)))), + + group: + html.tag('select', {id: 'contribution-group'}, [ + html.tag('option', {value: '-'}, + language.$('artistRollingWindowPage.contributionGroup.all')), + + stitchArrays({ + names: data.categoryGroupNames, + directories: data.categoryGroupDirectories, + }).map(({names, directories}, categoryIndex) => [ + html.tag('hr'), + + stitchArrays({name: names, directory: directories}) + .map(({name, directory}, groupIndex) => + html.tag('option', {value: directory}, + categoryIndex === data.firstGroupCategoryIndex && + groupIndex === data.firstGroupIndex && + {selected: true}, + + language.$('artistRollingWindowPage.contributionGroup.group', { + group: name, + }))), + ]), + ]), + })), + + html.tag('p', {id: 'timeframe-selection-info'}, [ + html.tag('span', {id: 'timeframe-selection-some'}, + {style: 'display: none'}, + + language.$('artistRollingWindowPage.timeframeSelectionLine', { + contributions: + html.tag('b', {id: 'timeframe-selection-contribution-count'}), + + timeframes: + html.tag('b', {id: 'timeframe-selection-timeframe-count'}), + + firstDate: + html.tag('b', {id: 'timeframe-selection-first-date'}), + + lastDate: + html.tag('b', {id: 'timeframe-selection-last-date'}), + })), + + html.tag('span', {id: 'timeframe-selection-none'}, + {style: 'display: none'}, + language.$('artistRollingWindowPage.timeframeSelectionLine.none')), + ]), + + html.tag('p', {id: 'timeframe-selection-control'}, + {style: 'display: none'}, + + language.$('artistRollingWindowPage.timeframeSelectionControl', { + timeframes: + html.tag('select', {id: 'timeframe-selection-menu'}), + + previous: + html.tag('a', {id: 'timeframe-selection-previous'}, + {href: '#'}, + language.$('artistRollingWindowPage.timeframeSelectionControl.previous')), + + next: + html.tag('a', {id: 'timeframe-selection-next'}, + {href: '#'}, + language.$('artistRollingWindowPage.timeframeSelectionControl.next')), + })), + + html.tag('div', {id: 'timeframe-source-area'}, [ + html.tag('p', {id: 'timeframe-empty'}, + {style: 'display: none'}, + language.$('artistRollingWindowPage.emptyTimeframeLine')), + + relations.sourceGrid.slots({ + attributes: {style: 'display: none'}, + + lazy: true, + + links: + relations.sourceGridLinks.map(link => + link.slot('attributes', {target: '_blank'})), + + names: + data.sourceGridNames, + + images: + relations.sourceGridImages, + + info: + stitchArrays({ + contributionKinds: data.sourceGridContributionKinds, + contributionDates: data.sourceGridContributionDates, + groupDirectories: data.sourceGridGroupDirectories, + groupNames: data.sourceGridGroupNames, + }).map(({ + contributionKinds, + contributionDates, + groupDirectories, + groupNames, + }) => [ + stitchArrays({ + directory: groupDirectories, + name: groupNames, + }).map(({directory, name}) => + html.tag('data', {class: 'contribution-group'}, + {value: directory}, + name)), + + stitchArrays({ + kind: contributionKinds, + date: contributionDates, + }).map(({kind, date}) => + html.tag('time', {class: `${kind}-contribution-date`}, + {datetime: date.toUTCString()}, + language.formatDate(date))), + ]), + }), + ]), + ], + + navLinkStyle: 'hierarchical', + navLinks: + relations.artistNavLinks + .slots({ + showExtraLinks: true, + currentExtra: 'rolling-window', + }) + .content, + }), +} diff --git a/src/content/dependencies/generateBackToAlbumLink.js b/src/content/dependencies/generateBackToAlbumLink.js index 6648b463..08d33348 100644 --- a/src/content/dependencies/generateBackToAlbumLink.js +++ b/src/content/dependencies/generateBackToAlbumLink.js @@ -1,7 +1,4 @@ export default { - contentDependencies: ['linkAlbum'], - extraDependencies: ['language'], - relations: (relation, track) => ({ trackLink: relation('linkAlbum', track), diff --git a/src/content/dependencies/generateBackToTrackLink.js b/src/content/dependencies/generateBackToTrackLink.js index 8677d811..90dfb6d5 100644 --- a/src/content/dependencies/generateBackToTrackLink.js +++ b/src/content/dependencies/generateBackToTrackLink.js @@ -1,7 +1,4 @@ export default { - contentDependencies: ['linkTrack'], - extraDependencies: ['language'], - relations: (relation, track) => ({ trackLink: relation('linkTrack', track), diff --git a/src/content/dependencies/generateBanner.js b/src/content/dependencies/generateBanner.js index 15eb08eb..509b15c2 100644 --- a/src/content/dependencies/generateBanner.js +++ b/src/content/dependencies/generateBanner.js @@ -1,6 +1,4 @@ export default { - extraDependencies: ['html', 'to'], - slots: { path: { validate: v => v.validateArrayItems(v.isString), diff --git a/src/content/dependencies/generateCollapsedContentEntrySection.js b/src/content/dependencies/generateCollapsedContentEntrySection.js new file mode 100644 index 00000000..aec5fe28 --- /dev/null +++ b/src/content/dependencies/generateCollapsedContentEntrySection.js @@ -0,0 +1,37 @@ +export default { + relations: (relation, entries, thing) => ({ + contentContentHeading: + relation('generateContentContentHeading', thing), + + entries: + entries + .map(entry => relation('generateCommentaryEntry', entry)), + }), + + slots: { + id: {type: 'string'}, + string: {type: 'string'}, + }, + + generate: (relations, slots, {html}) => + html.tag('details', + {[html.onlyIfContent]: true}, + + slots.id && [ + {class: 'memorable'}, + {'data-memorable-id': slots.id}, + ], + + [ + relations.contentContentHeading.slots({ + attributes: [ + slots.id && {id: slots.id}, + ], + + string: slots.string, + summary: true, + }), + + relations.entries, + ]), +}; diff --git a/src/content/dependencies/generateColorStyleAttribute.js b/src/content/dependencies/generateColorStyleAttribute.js index 03d95ac5..277ec434 100644 --- a/src/content/dependencies/generateColorStyleAttribute.js +++ b/src/content/dependencies/generateColorStyleAttribute.js @@ -1,7 +1,4 @@ export default { - contentDependencies: ['generateColorStyleVariables'], - extraDependencies: ['html'], - relations: (relation) => ({ colorVariables: relation('generateColorStyleVariables'), diff --git a/src/content/dependencies/generateColorStyleRules.js b/src/content/dependencies/generateColorStyleRules.js deleted file mode 100644 index c412b8f2..00000000 --- a/src/content/dependencies/generateColorStyleRules.js +++ /dev/null @@ -1,42 +0,0 @@ -export default { - contentDependencies: ['generateColorStyleVariables'], - extraDependencies: ['html'], - - relations: (relation) => ({ - variables: - relation('generateColorStyleVariables'), - }), - - data: (color) => ({ - color: - color ?? null, - }), - - slots: { - color: { - validate: v => v.isColor, - }, - }, - - generate(data, relations, slots) { - const color = data.color ?? slots.color; - - if (!color) { - return ''; - } - - return [ - `:root {`, - ...( - relations.variables - .slots({ - color, - context: 'page-root', - mode: 'property-list', - }) - .content - .map(line => line + ';')), - `}`, - ].join('\n'); - }, -}; diff --git a/src/content/dependencies/generateColorStyleTag.js b/src/content/dependencies/generateColorStyleTag.js new file mode 100644 index 00000000..b378fd1d --- /dev/null +++ b/src/content/dependencies/generateColorStyleTag.js @@ -0,0 +1,48 @@ +export default { + relations: (relation) => ({ + styleTag: + relation('generateStyleTag'), + + variables: + relation('generateColorStyleVariables'), + }), + + data: (color) => ({ + color: + color ?? null, + }), + + slots: { + color: { + validate: v => v.isColor, + }, + }, + + generate(data, relations, slots, {html}) { + const color = + data.color ?? slots.color; + + if (!color) { + return html.blank(); + } + + return relations.styleTag.slots({ + attributes: [ + {class: 'color-style'}, + {'data-color': color}, + ], + + rules: [ + { + select: ':root', + declare: + relations.variables.slots({ + color, + context: 'page-root', + mode: 'declarations', + }).content, + }, + ], + }); + }, +}; diff --git a/src/content/dependencies/generateColorStyleVariables.js b/src/content/dependencies/generateColorStyleVariables.js index 5270dbe4..0865ed3e 100644 --- a/src/content/dependencies/generateColorStyleVariables.js +++ b/src/content/dependencies/generateColorStyleVariables.js @@ -1,6 +1,4 @@ export default { - extraDependencies: ['html', 'getColors'], - slots: { color: { validate: v => v.isColor, @@ -18,7 +16,7 @@ export default { }, mode: { - validate: v => v.is('style', 'property-list'), + validate: v => v.is('style', 'declarations'), default: 'style', }, }, @@ -50,15 +48,15 @@ export default { `--shadow-color: ${shadow}`, ]; - let selectedProperties; + let selectedDeclarations; switch (slots.context) { case 'any-content': - selectedProperties = anyContent; + selectedDeclarations = anyContent; break; case 'image-box': - selectedProperties = [ + selectedDeclarations = [ `--primary-color: ${primary}`, `--dim-color: ${dim}`, `--deep-color: ${deep}`, @@ -67,14 +65,14 @@ export default { break; case 'page-root': - selectedProperties = [ + selectedDeclarations = [ ...anyContent, `--page-primary-color: ${primary}`, ]; break; case 'primary-only': - selectedProperties = [ + selectedDeclarations = [ `--primary-color: ${primary}`, ]; break; @@ -82,10 +80,10 @@ export default { switch (slots.mode) { case 'style': - return selectedProperties.join('; '); + return selectedDeclarations.join('; '); - case 'property-list': - return selectedProperties; + case 'declarations': + return selectedDeclarations.map(declaration => declaration + ';'); } }, }; diff --git a/src/content/dependencies/generateCommentaryContentHeading.js b/src/content/dependencies/generateCommentaryContentHeading.js new file mode 100644 index 00000000..691762aa --- /dev/null +++ b/src/content/dependencies/generateCommentaryContentHeading.js @@ -0,0 +1,43 @@ +import {empty} from '#sugar'; + +export default { + query: (thing) => ({ + entries: + (thing.isTrack + ? [...thing.commentary, ...thing.commentaryFromMainRelease] + : thing.commentary), + }), + + relations: (relation, _query, thing) => ({ + contentContentHeading: + relation('generateContentContentHeading', thing), + }), + + data: (query, _thing) => ({ + hasWikiEditorCommentary: + query.entries.some(entry => entry.isWikiEditorCommentary), + + onlyWikiEditorCommentary: + !empty(query.entries) && + query.entries.every(entry => entry.isWikiEditorCommentary), + + hasAnyCommentary: + !empty(query.entries), + }), + + generate: (data, relations, {language}) => + relations.contentContentHeading.slots({ + // It's #artist-commentary for legacy reasons... Sorry... + attributes: {id: 'artist-commentary'}, + + string: + language.encapsulate('misc.artistCommentary', capsule => + (data.onlyWikiEditorCommentary + ? language.encapsulate(capsule, 'onlyWikiCommentary') + : data.hasWikiEditorCommentary + ? language.encapsulate(capsule, 'withWikiCommentary') + : data.hasAnyCommentary + ? capsule + : null)), + }), +}; diff --git a/src/content/dependencies/generateCommentaryEntry.js b/src/content/dependencies/generateCommentaryEntry.js index c93020f3..38eb6b43 100644 --- a/src/content/dependencies/generateCommentaryEntry.js +++ b/src/content/dependencies/generateCommentaryEntry.js @@ -1,25 +1,16 @@ import {empty} from '#sugar'; export default { - contentDependencies: [ - 'generateCommentaryEntryDate', - 'generateColorStyleAttribute', - 'linkArtist', - 'transformContent', - ], - - extraDependencies: ['html', 'language'], - relations: (relation, entry) => ({ artistLinks: - (!empty(entry.artists) && !entry.artistDisplayText + (!empty(entry.artists) && !entry.artistText ? entry.artists .map(artist => relation('linkArtist', artist)) : null), artistsContent: - (entry.artistDisplayText - ? relation('transformContent', entry.artistDisplayText) + (entry.artistText + ? relation('transformContent', entry.artistText) : null), annotationContent: @@ -39,11 +30,16 @@ export default { relation('generateCommentaryEntryDate', entry), }), + data: (entry) => ({ + isWikiEditorCommentary: + entry.isWikiEditorCommentary, + }), + slots: { color: {validate: v => v.isColor}, }, - generate: (relations, slots, {html, language}) => + generate: (data, relations, slots, {html, language}) => language.encapsulate('misc.artistCommentary.entry', entryCapsule => html.tags([ html.tag('p', {class: 'commentary-entry-heading'}, @@ -107,6 +103,9 @@ export default { relations.colorStyle.clone() .slot('color', slots.color), + data.isWikiEditorCommentary && + {class: 'wiki-commentary'}, + relations.bodyContent.slot('mode', 'multiline')), ])), }; diff --git a/src/content/dependencies/generateCommentaryEntryDate.js b/src/content/dependencies/generateCommentaryEntryDate.js index f1cf5cb3..e924f244 100644 --- a/src/content/dependencies/generateCommentaryEntryDate.js +++ b/src/content/dependencies/generateCommentaryEntryDate.js @@ -1,7 +1,4 @@ export default { - contentDependencies: ['generateTextWithTooltip', 'generateTooltip'], - extraDependencies: ['html', 'language'], - relations: (relation, _entry) => ({ textWithTooltip: relation('generateTextWithTooltip'), diff --git a/src/content/dependencies/generateCommentaryIndexPage.js b/src/content/dependencies/generateCommentaryIndexPage.js index d68ba42e..8cc30913 100644 --- a/src/content/dependencies/generateCommentaryIndexPage.js +++ b/src/content/dependencies/generateCommentaryIndexPage.js @@ -1,13 +1,11 @@ +import multilingualWordCount from 'word-count'; + import {sortChronologically} from '#sort'; import {accumulateSum, filterMultipleArrays, stitchArrays} from '#sugar'; export default { - contentDependencies: ['generatePageLayout', 'linkAlbumCommentary'], - extraDependencies: ['html', 'language', 'wikiData'], - - sprawl({albumData}) { - return {albumData}; - }, + sprawl: ({albumData}) => + ({albumData}), query(sprawl) { const query = {}; @@ -21,44 +19,52 @@ export default { .filter(({commentary}) => commentary) .flatMap(({commentary}) => commentary)); - query.wordCounts = - entries.map(entries => - accumulateSum( - entries, - entry => entry.body.split(' ').length)); + query.bodies = + entries.map(entries => entries.map(entry => entry.body)); query.entryCounts = entries.map(entries => entries.length); - filterMultipleArrays(query.albums, query.wordCounts, query.entryCounts, - (album, wordCount, entryCount) => entryCount >= 1); + filterMultipleArrays(query.albums, query.bodies, query.entryCounts, + (album, bodies, entryCount) => entryCount >= 1); return query; }, - relations(relation, query) { - return { - layout: - relation('generatePageLayout'), + relations: (relation, query) => ({ + layout: + relation('generatePageLayout'), - albumLinks: - query.albums - .map(album => relation('linkAlbumCommentary', album)), - }; - }, + albumLinks: + query.albums + .map(album => relation('linkAlbumCommentary', album)), - data(query) { - return { - wordCounts: query.wordCounts, - entryCounts: query.entryCounts, + albumBodies: + query.bodies + .map(bodies => bodies + .map(body => relation('transformContent', body))), + }), - totalWordCount: accumulateSum(query.wordCounts), - totalEntryCount: accumulateSum(query.entryCounts), - }; - }, + data: (query) => ({ + entryCounts: query.entryCounts, + totalEntryCount: accumulateSum(query.entryCounts), + }), - generate: (data, relations, {html, language}) => - language.encapsulate('commentaryIndex', pageCapsule => + generate(data, relations, {html, language}) { + const wordCounts = + relations.albumBodies.map(bodies => + accumulateSum(bodies, body => + multilingualWordCount( + html.resolve( + body.slot('mode', 'multiline'), + {normalize: 'plain'})))); + + const totalWordCount = + accumulateSum(wordCounts); + + const {entryCounts, totalEntryCount} = data; + + return language.encapsulate('commentaryIndex', pageCapsule => relations.layout.slots({ title: language.$(pageCapsule, 'title'), @@ -69,11 +75,11 @@ export default { html.tag('p', language.$(pageCapsule, 'infoLine', { words: html.tag('b', - language.formatWordCount(data.totalWordCount, {unit: true})), + language.formatWordCount(totalWordCount, {unit: true})), entries: html.tag('b', - language.countCommentaryEntries(data.totalEntryCount, {unit: true})), + language.countCommentaryEntries(totalEntryCount, {unit: true})), })), language.encapsulate(pageCapsule, 'albumList', listCapsule => [ @@ -83,8 +89,8 @@ export default { html.tag('ul', stitchArrays({ albumLink: relations.albumLinks, - wordCount: data.wordCounts, - entryCount: data.entryCounts, + wordCount: wordCounts, + entryCount: entryCounts, }).map(({albumLink, wordCount, entryCount}) => html.tag('li', language.$(listCapsule, 'item', { @@ -100,5 +106,6 @@ export default { {auto: 'home'}, {auto: 'current'}, ], - })), + })); + }, }; diff --git a/src/content/dependencies/generateContentContentHeading.js b/src/content/dependencies/generateContentContentHeading.js new file mode 100644 index 00000000..9ed2d9f0 --- /dev/null +++ b/src/content/dependencies/generateContentContentHeading.js @@ -0,0 +1,73 @@ +export default { + relations: (relation, _thing) => ({ + contentHeading: + relation('generateContentHeading'), + }), + + data: (thing) => ({ + name: + (thing + ? thing.name + : null), + }), + + slots: { + attributes: { + type: 'attributes', + mutable: false, + }, + + string: { + type: 'string', + }, + + summary: { + type: 'boolean', + default: false, + }, + }, + + generate: (data, relations, slots, {html, language}) => + relations.contentHeading.slots({ + attributes: slots.attributes, + + title: + (() => { + if (!slots.string) return html.blank(); + + const options = {}; + + if (slots.summary) { + options.cue = + html.tag('span', {class: 'cue'}, + language.$(slots.string, 'cue')); + } + + if (data.name) { + options.thing = html.tag('i', data.name); + } + + if (slots.summary) { + return html.tags([ + html.tag('span', {class: 'when-open'}, + language.$(slots.string, options)), + + html.tag('span', {class: 'when-collapsed'}, + language.$(slots.string, 'collapsed', options)), + ]); + } else { + return language.$(slots.string, options); + } + })(), + + stickyTitle: + (slots.string + ? language.$(slots.string, 'sticky') + : html.blank()), + + tag: + (slots.summary + ? 'summary' + : 'p'), + }), +}; diff --git a/src/content/dependencies/generateContentHeading.js b/src/content/dependencies/generateContentHeading.js index f52bc043..a7cf201f 100644 --- a/src/content/dependencies/generateContentHeading.js +++ b/src/content/dependencies/generateContentHeading.js @@ -1,7 +1,5 @@ export default { extraDependencies: ['html'], - contentDependencies: ['generateColorStyleAttribute'], - relations: (relation) => ({ colorStyle: relation('generateColorStyleAttribute'), }), diff --git a/src/content/dependencies/generateContributionList.js b/src/content/dependencies/generateContributionList.js index d1c3de0f..3716bcd6 100644 --- a/src/content/dependencies/generateContributionList.js +++ b/src/content/dependencies/generateContributionList.js @@ -1,7 +1,4 @@ export default { - contentDependencies: ['linkContribution'], - extraDependencies: ['html'], - relations: (relation, contributions) => ({ contributionLinks: contributions diff --git a/src/content/dependencies/generateContributionTooltip.js b/src/content/dependencies/generateContributionTooltip.js index 3a31014d..fd16371b 100644 --- a/src/content/dependencies/generateContributionTooltip.js +++ b/src/content/dependencies/generateContributionTooltip.js @@ -1,21 +1,79 @@ -export default { - contentDependencies: [ - 'generateContributionTooltipChronologySection', - 'generateContributionTooltipExternalLinkSection', - 'generateTooltip', - ], +function compareReleaseContributions(a, b) { + if (a === b) { + return true; + } + + const {previous: aPrev, next: aNext} = getSiblings(a); + const {previous: bPrev, next: bNext} = getSiblings(b); + + const effective = contrib => + (contrib?.thing.isAlbum && contrib.thing.style === 'single' + ? contrib.thing.tracks[0] + : contrib?.thing); + + return ( + effective(aPrev) === effective(bPrev) && + effective(aNext) === effective(bNext) + ); +} - extraDependencies: ['html'], +function getSiblings(contribution) { + let previous = contribution; + while (previous && previous.thing === contribution.thing) { + previous = previous.previousBySameArtist; + } - relations: (relation, contribution) => ({ + let next = contribution; + while (next && next.thing === contribution.thing) { + next = next.nextBySameArtist; + } + + return {previous, next}; +} + +export default { + query: (contribution) => ({ + albumArtistContribution: + (contribution.thing.isTrack + ? contribution.thing.album.artistContribs + .find(artistContrib => artistContrib.artist === contribution.artist) + : null), + }), + + relations: (relation, query, contribution) => ({ tooltip: relation('generateTooltip'), externalLinkSection: relation('generateContributionTooltipExternalLinkSection', contribution), - chronologySection: + ownChronologySection: relation('generateContributionTooltipChronologySection', contribution), + + artistReleaseChronologySection: + (query.albumArtistContribution + ? relation('generateContributionTooltipChronologySection', + query.albumArtistContribution) + : null), + }), + + data: (query, contribution) => ({ + artistName: + contribution.artist.name, + + isAlbumArtistContribution: + contribution.thing.isAlbum && + contribution.thingProperty === 'artistContribs', + + isSingleTrackArtistContribution: + contribution.thing.isTrack && + contribution.thingProperty === 'artistContribs' && + contribution.thing.album.style === 'single', + + artistReleaseChronologySectionDiffers: + (query.albumArtistContribution + ? !compareReleaseContributions(contribution, query.albumArtistContribution) + : null), }), slots: { @@ -25,24 +83,64 @@ export default { chronologyKind: {type: 'string'}, }, - generate: (relations, slots, {html}) => - relations.tooltip.slots({ - attributes: - {class: 'contribution-tooltip'}, - - contentAttributes: { - [html.joinChildren]: - html.tag('span', {class: 'tooltip-divider'}), - }, - - content: [ - slots.showExternalLinks && - relations.externalLinkSection, - - slots.showChronology && - relations.chronologySection.slots({ - kind: slots.chronologyKind, - }), - ], - }), + generate: (data, relations, slots, {html, language}) => + language.encapsulate('misc.artistLink', capsule => + relations.tooltip.slots({ + attributes: + {class: 'contribution-tooltip'}, + + contentAttributes: { + [html.joinChildren]: + html.tag('span', {class: 'tooltip-divider'}), + }, + + content: [ + slots.showExternalLinks && + relations.externalLinkSection, + + slots.showChronology && + language.encapsulate(capsule, 'chronology', capsule => { + const chronologySections = []; + + if (data.isAlbumArtistContribution) { + relations.ownChronologySection.setSlots({ + kind: 'release', + heading: + language.$(capsule, 'heading.artistReleases', { + artist: data.artistName, + }), + }); + } else { + relations.ownChronologySection.setSlot('kind', slots.chronologyKind); + } + + if ( + data.isSingleTrackArtistContribution && + relations.artistReleaseChronologySection + ) { + relations.artistReleaseChronologySection.setSlot('kind', 'release'); + + relations.artistReleaseChronologySection.setSlot('heading', + language.$(capsule, 'heading.artistReleases', { + artist: data.artistName, + })); + + chronologySections.push(relations.artistReleaseChronologySection); + + if (data.artistReleaseChronologySectionDiffers) { + relations.ownChronologySection.setSlot('heading', + language.$(capsule, 'heading.artistTracks', { + artist: data.artistName, + })); + + chronologySections.push(relations.ownChronologySection); + } + } else { + chronologySections.push(relations.ownChronologySection); + } + + return chronologySections; + }), + ], + })), }; diff --git a/src/content/dependencies/generateContributionTooltipChronologySection.js b/src/content/dependencies/generateContributionTooltipChronologySection.js index 378c0e1c..e4b9bfda 100644 --- a/src/content/dependencies/generateContributionTooltipChronologySection.js +++ b/src/content/dependencies/generateContributionTooltipChronologySection.js @@ -1,36 +1,33 @@ -import Thing from '#thing'; - function getName(thing) { if (!thing) { return null; } - const referenceType = thing.constructor[Thing.referenceType]; - - if (referenceType === 'artwork') { + if (thing.isArtwork) { return thing.thing.name; } return thing.name; } -export default { - contentDependencies: ['linkAnythingMan'], - extraDependencies: ['html', 'language'], +function getSiblings(contribution) { + let previous = contribution; + while (previous && previous.thing === contribution.thing) { + previous = previous.previousBySameArtist; + } - query(contribution) { - let previous = contribution; - while (previous && previous.thing === contribution.thing) { - previous = previous.previousBySameArtist; - } + let next = contribution; + while (next && next.thing === contribution.thing) { + next = next.nextBySameArtist; + } - let next = contribution; - while (next && next.thing === contribution.thing) { - next = next.nextBySameArtist; - } + return {previous, next}; +} - return {previous, next}; - }, +export default { + query: (contribution) => ({ + ...getSiblings(contribution), + }), relations: (relation, query, _contribution) => ({ previousLink: @@ -53,23 +50,19 @@ export default { }), slots: { - kind: { - validate: v => - v.is( - 'album', - 'bannerArt', - 'coverArt', - 'flash', - 'track', - 'trackArt', - 'trackContribution', - 'wallpaperArt'), - }, + heading: {type: 'html', mutable: false}, + kind: {type: 'string'}, }, generate: (data, relations, slots, {html, language}) => language.encapsulate('misc.artistLink.chronology', capsule => html.tags([ + html.tag('span', {class: 'chronology-heading'}, + {[html.onlyIfContent]: true}, + {[html.onlyIfSiblings]: true}, + + slots.heading), + html.tags([ relations.previousLink?.slots({ attributes: {class: 'chronology-link'}, diff --git a/src/content/dependencies/generateContributionTooltipExternalLinkSection.js b/src/content/dependencies/generateContributionTooltipExternalLinkSection.js index 4f9a23ed..210db1e9 100644 --- a/src/content/dependencies/generateContributionTooltipExternalLinkSection.js +++ b/src/content/dependencies/generateContributionTooltipExternalLinkSection.js @@ -1,14 +1,6 @@ import {stitchArrays} from '#sugar'; export default { - contentDependencies: [ - 'generateExternalHandle', - 'generateExternalIcon', - 'generateExternalPlatform', - ], - - extraDependencies: ['html', 'language'], - relations: (relation, contribution) => ({ icons: contribution.artist.urls diff --git a/src/content/dependencies/generateCoverArtwork.js b/src/content/dependencies/generateCoverArtwork.js index 2bff4643..616b3c95 100644 --- a/src/content/dependencies/generateCoverArtwork.js +++ b/src/content/dependencies/generateCoverArtwork.js @@ -1,15 +1,8 @@ export default { - contentDependencies: [ - 'generateCoverArtworkArtTagDetails', - 'generateCoverArtworkArtistDetails', - 'generateCoverArtworkOriginDetails', - 'generateCoverArtworkReferenceDetails', - 'image', - ], - - extraDependencies: ['html'], - relations: (relation, artwork) => ({ + colorStyleAttribute: + relation('generateColorStyleAttribute'), + image: relation('image', artwork), @@ -30,18 +23,27 @@ export default { attachAbove: artwork.attachAbove, + attachedArtworkIsMainArtwork: + (artwork.attachAbove + ? artwork.attachedArtwork.isMainArtwork + : null), + color: artwork.thing.color ?? null, dimensions: artwork.dimensions, + + style: + artwork.style, }), slots: { alt: {type: 'string'}, color: { - validate: v => v.isColor, + validate: v => v.anyOf(v.isBoolean, v.isColor), + default: false, }, mode: { @@ -63,10 +65,15 @@ export default { generate(data, relations, slots, {html}) { const {image} = relations; - image.setSlots({ - color: slots.color ?? data.color, - alt: slots.alt, - }); + const imgAttributes = html.attributes(); + + if (data.style) { + imgAttributes.add('style', data.style.split('\n').join(' ')); + } + + image.setSlot('imgAttributes', imgAttributes); + + image.setSlot('alt', slots.alt); const square = (data.dimensions @@ -79,6 +86,22 @@ export default { image.setSlot('dimensions', data.dimensions); } + const attributes = html.attributes(); + + let color = null; + if (typeof slots.color === 'boolean') { + if (slots.color) { + color = data.color; + } + } else if (slots.color) { + color = slots.color; + } + + if (color) { + relations.colorStyleAttribute.setSlot('color', color); + attributes.add(relations.colorStyleAttribute); + } + return html.tags([ data.attachAbove && html.tag('div', {class: 'cover-artwork-joiner'}), @@ -87,12 +110,42 @@ export default { slots.mode === 'commentary' && {class: 'commentary-art'}, + data.attachAbove && + data.attachedArtworkIsMainArtwork && + {class: 'attached-artwork-is-main-artwork'}, + + attributes, + (slots.mode === 'primary' ? [ relations.image.slots({ thumb: 'medium', reveal: true, link: true, + + responsiveThumb: true, + responsiveSizes: + // No clamp(), min(), or max() here because Safari. + // The boundaries here are mostly experimental, apart from + // the ones which flat-out switch layouts. + + // Layout - Thin (phones) + // Most of viewport width + '(max-width: 600px) 90vw,\n' + + + // Layout - Medium + // Sidebar is hidden; content area is by definition + // most of the viewport + '(max-width: 640px) 220px,\n' + + '(max-width: 800px) 36vw,\n' + + '(max-width: 850px) 280px,\n' + + + // Layout - Wide + // Sidebar is visible; content area has its own maximum + // Assume the sidebar is at minimum width + '(max-width: 880px) 220px,\n' + + '(max-width: 1050pz) calc(0.40 * (90vw - 150px - 10px)),\n' + + '280px', }), slots.showOriginDetails && diff --git a/src/content/dependencies/generateCoverArtworkArtTagDetails.js b/src/content/dependencies/generateCoverArtworkArtTagDetails.js index 4d908665..50571a4f 100644 --- a/src/content/dependencies/generateCoverArtworkArtTagDetails.js +++ b/src/content/dependencies/generateCoverArtworkArtTagDetails.js @@ -5,9 +5,6 @@ function linkable(tag) { } export default { - contentDependencies: ['linkArtTagGallery'], - extraDependencies: ['html', 'language'], - query: (artwork) => ({ linkableArtTags: artwork.artTags.filter(linkable), diff --git a/src/content/dependencies/generateCoverArtworkArtistDetails.js b/src/content/dependencies/generateCoverArtworkArtistDetails.js index 3ead80ab..2773c6fc 100644 --- a/src/content/dependencies/generateCoverArtworkArtistDetails.js +++ b/src/content/dependencies/generateCoverArtworkArtistDetails.js @@ -1,7 +1,4 @@ export default { - contentDependencies: ['linkArtistGallery'], - extraDependencies: ['html', 'language'], - relations: (relation, artwork) => ({ artistLinks: artwork.artistContribs diff --git a/src/content/dependencies/generateCoverArtworkOriginDetails.js b/src/content/dependencies/generateCoverArtworkOriginDetails.js index 3908414f..e489eea6 100644 --- a/src/content/dependencies/generateCoverArtworkOriginDetails.js +++ b/src/content/dependencies/generateCoverArtworkOriginDetails.js @@ -1,19 +1,5 @@ -import Thing from '#thing'; - export default { - contentDependencies: [ - 'generateArtistCredit', - 'generateAbsoluteDatetimestamp', - 'linkAlbum', - 'transformContent', - ], - - extraDependencies: ['html', 'language', 'pagePath'], - query: (artwork) => ({ - artworkThingType: - artwork.thing.constructor[Thing.referenceType], - attachedArtistContribs: (artwork.attachedArtwork ? artwork.attachedArtwork.artistContribs @@ -29,15 +15,18 @@ export default { source: relation('transformContent', artwork.source), + originDetails: + relation('transformContent', artwork.originDetails), + albumLink: - (query.artworkThingType === 'album' + (artwork.thing.isAlbum ? relation('linkAlbum', artwork.thing) : null), datetimestamp: - (artwork.date && artwork.date !== artwork.thing.date - ? relation('generateAbsoluteDatetimestamp', artwork.date) - : null), + relation('generateAbsoluteDatetimestamp', + artwork.date, + artwork.thing.date), }), @@ -45,23 +34,26 @@ export default { label: artwork.label, - artworkThingType: - query.artworkThingType, + forAlbum: + artwork.thing.isAlbum, + + forSingleStyleAlbum: + artwork.thing.isAlbum && + artwork.thing.style === 'single', + + showFilename: + artwork.showFilename, }), generate: (data, relations, {html, language, pagePath}) => language.encapsulate('misc.coverArtwork', capsule => html.tag('p', {class: 'image-details'}, {[html.onlyIfContent]: true}, - {[html.joinChildren]: html.tag('br')}, {class: 'origin-details'}, (() => { - relations.datetimestamp?.setSlots({ - style: 'year', - tooltip: true, - }); + relations.datetimestamp.setSlot('style', 'year-difference'); const artworkBy = language.encapsulate(capsule, 'artworkBy', workingCapsule => { @@ -72,7 +64,7 @@ export default { workingOptions.label = data.label; } - if (relations.datetimestamp) { + if (!html.isBlank(relations.datetimestamp)) { workingCapsule += '.withYear'; workingOptions.year = relations.datetimestamp; } @@ -94,7 +86,8 @@ export default { const trackArtFromAlbum = pagePath[0] === 'track' && - data.artworkThingType === 'album' && + data.forAlbum && + !data.forSingleStyleAlbum && language.$(capsule, 'trackArtFromAlbum', { album: relations.albumLink.slot('color', false), @@ -112,7 +105,7 @@ export default { workingOptions.label = data.label; } - if (html.isBlank(artworkBy) && relations.datetimestamp) { + if (html.isBlank(artworkBy) && !html.isBlank(relations.datetimestamp)) { workingCapsule += '.withYear'; workingOptions.year = relations.datetimestamp; } @@ -129,7 +122,7 @@ export default { label: data.label, }; - if (relations.datetimestamp) { + if (!html.isBlank(relations.datetimestamp)) { workingCapsule += '.withYear'; workingOptions.year = relations.datetimestamp; } @@ -146,12 +139,35 @@ export default { year: relations.datetimestamp, }); + const originDetailsLine = + html.tag('span', {class: 'origin-details-line'}, + {[html.onlyIfContent]: true}, + + relations.originDetails.slots({ + mode: 'inline', + absorbPunctuationFollowingExternalLinks: false, + })); + + const filenameLine = + html.tag('span', {class: 'filename-line'}, + {[html.onlyIfContent]: true}, + + html.tag('code', {class: 'filename'}, + {[html.onlyIfContent]: true}, + + language.sanitize(data.showFilename))); + return [ - artworkBy, - trackArtFromAlbum, - source, - label, - year, + html.tags([ + artworkBy, + trackArtFromAlbum, + source, + label, + year, + ], {[html.joinChildren]: html.tag('br')}), + + originDetailsLine, + filenameLine, ]; })())), }; diff --git a/src/content/dependencies/generateCoverArtworkReferenceDetails.js b/src/content/dependencies/generateCoverArtworkReferenceDetails.js index 035ab586..d4e4e7e4 100644 --- a/src/content/dependencies/generateCoverArtworkReferenceDetails.js +++ b/src/content/dependencies/generateCoverArtworkReferenceDetails.js @@ -1,7 +1,4 @@ export default { - contentDependencies: ['linkReferencedArtworks', 'linkReferencingArtworks'], - extraDependencies: ['html', 'language'], - relations: (relation, artwork) => ({ referencedArtworksLink: relation('linkReferencedArtworks', artwork), diff --git a/src/content/dependencies/generateCoverCarousel.js b/src/content/dependencies/generateCoverCarousel.js index 430f651e..1ffeff8e 100644 --- a/src/content/dependencies/generateCoverCarousel.js +++ b/src/content/dependencies/generateCoverCarousel.js @@ -2,8 +2,6 @@ import {empty, repeat, stitchArrays} from '#sugar'; import {getCarouselLayoutForNumberOfItems} from '#wiki-data'; export default { - extraDependencies: ['html'], - slots: { images: {validate: v => v.strictArrayOf(v.isHTML)}, links: {validate: v => v.strictArrayOf(v.isHTML)}, @@ -19,7 +17,7 @@ export default { }); if (empty(stitched)) { - return; + return html.blank(); } const layout = getCarouselLayoutForNumberOfItems(stitched.length); diff --git a/src/content/dependencies/generateCoverGrid.js b/src/content/dependencies/generateCoverGrid.js index 29ac08b7..091833a9 100644 --- a/src/content/dependencies/generateCoverGrid.js +++ b/src/content/dependencies/generateCoverGrid.js @@ -1,20 +1,23 @@ -import {stitchArrays} from '#sugar'; +import {empty, stitchArrays, unique} from '#sugar'; export default { - contentDependencies: ['generateGridActionLinks'], - extraDependencies: ['html', 'language'], + relations: (relation) => ({ + actionLinks: + relation('generateGridActionLinks'), - relations(relation) { - return { - actionLinks: relation('generateGridActionLinks'), - }; - }, + expando: + relation('generateGridExpando'), + }), slots: { + attributes: {type: 'attributes', mutable: false}, + images: {validate: v => v.strictArrayOf(v.isHTML)}, links: {validate: v => v.strictArrayOf(v.isHTML)}, names: {validate: v => v.strictArrayOf(v.isHTML)}, info: {validate: v => v.strictArrayOf(v.isHTML)}, + tab: {validate: v => v.strictArrayOf(v.isHTML)}, + notFromThisGroup: {validate: v => v.strictArrayOf(v.isBoolean)}, // Differentiating from sparseArrayOf here - this list of classes should // have the same length as the items above, i.e. nulls aren't going to be @@ -29,34 +32,115 @@ export default { v.isString))), }, + itemAttributes: { + validate: v => + v.strictArrayOf( + v.optional(v.isAttributes)), + }, + lazy: {validate: v => v.anyOf(v.isWholeNumber, v.isBoolean)}, actionLinks: {validate: v => v.sparseArrayOf(v.isHTML)}, + + revealAllWarnings: { + validate: v => v.looseArrayOf(v.isString), + }, + + bottomCaption: { + type: 'html', + mutable: false, + }, + + cutIndex: {validate: v => v.isWholeNumber}, }, generate: (relations, slots, {html, language}) => html.tag('div', {class: 'grid-listing'}, + slots.attributes, {[html.onlyIfContent]: true}, [ + !empty((slots.revealAllWarnings ?? []).filter(Boolean)) && + language.encapsulate('misc.coverGrid.revealAll', capsule => + html.tag('div', {class: 'reveal-all-container'}, + ((slots.tab ?? []) + .slice(0, 4) + .some(tab => tab && !html.isBlank(tab))) && + + {class: 'has-nearby-tab'}, + + html.tag('p', {class: 'reveal-all'}, [ + html.tag('a', {href: '#'}, [ + html.tag('span', {class: 'reveal-label'}, + language.$(capsule, 'reveal')), + + html.tag('span', {class: 'conceal-label'}, + {style: 'display: none'}, + language.$(capsule, 'conceal')), + ]), + + html.tag('br'), + + html.tag('span', {class: 'warnings'}, + language.$(capsule, 'warnings', { + warnings: + language.formatUnitList( + unique(slots.revealAllWarnings.filter(Boolean)) + .sort() + .map(warning => html.tag('b', warning))), + })), + ]))), + stitchArrays({ classes: slots.classes, + attributes: slots.itemAttributes, image: slots.images, link: slots.links, name: slots.names, info: slots.info, - }).map(({classes, image, link, name, info}, index) => + tab: slots.tab, + + notFromThisGroup: + slots.notFromThisGroup ?? + Array.from(slots.links).fill(null) + }).map(({ + classes, + attributes, + image, + link, + name, + info, + tab, + notFromThisGroup, + }, index) => link.slots({ attributes: [ + link.getSlotValue('attributes'), + {class: ['grid-item', 'box']}, + tab && + !html.isBlank(tab) && + {class: 'has-tab'}, + + attributes, + (classes ? {class: classes} : null), + + slots.cutIndex >= 1 && + index >= slots.cutIndex && + {class: 'hidden-by-expandable-cut'}, ], colorContext: 'image-box', content: [ + html.tag('span', + {[html.onlyIfContent]: true}, + + tab), + image.slots({ thumb: 'medium', square: true, @@ -71,7 +155,15 @@ export default { html.tag('span', {[html.onlyIfContent]: true}, - language.sanitize(name)), + (notFromThisGroup + ? language.encapsulate('misc.coverGrid.details.notFromThisGroup', capsule => + language.$(capsule, { + name, + marker: + html.tag('span', {class: 'grid-name-marker'}, + language.$(capsule, 'marker')), + })) + : language.sanitize(name))), html.tag('span', {[html.onlyIfContent]: true}, @@ -86,5 +178,17 @@ export default { relations.actionLinks .slot('actionLinks', slots.actionLinks), + + (slots.cutIndex >= 1 && + slots.cutIndex < slots.links.length + ? relations.expando.slots({ + caption: slots.bottomCaption, + }) + + : !html.isBlank(relations.bottomCaption) + ? html.tag('p', {class: 'grid-caption'}, + slots.caption) + + : html.blank()), ]), }; diff --git a/src/content/dependencies/generateDatetimestampTemplate.js b/src/content/dependencies/generateDatetimestampTemplate.js index a92d15fc..56b2e595 100644 --- a/src/content/dependencies/generateDatetimestampTemplate.js +++ b/src/content/dependencies/generateDatetimestampTemplate.js @@ -1,7 +1,4 @@ export default { - contentDependencies: ['generateTextWithTooltip'], - extraDependencies: ['html'], - relations: (relation) => ({ textWithTooltip: relation('generateTextWithTooltip'), diff --git a/src/content/dependencies/generateDotSwitcherTemplate.js b/src/content/dependencies/generateDotSwitcherTemplate.js index 22205922..561a44bc 100644 --- a/src/content/dependencies/generateDotSwitcherTemplate.js +++ b/src/content/dependencies/generateDotSwitcherTemplate.js @@ -1,6 +1,4 @@ export default { - extraDependencies: ['html'], - slots: { attributes: { type: 'attributes', diff --git a/src/content/dependencies/generateExternalHandle.js b/src/content/dependencies/generateExternalHandle.js index 8c0368a4..8653b177 100644 --- a/src/content/dependencies/generateExternalHandle.js +++ b/src/content/dependencies/generateExternalHandle.js @@ -1,8 +1,6 @@ import {isExternalLinkContext} from '#external-links'; export default { - extraDependencies: ['html', 'language'], - data: (url) => ({url}), slots: { diff --git a/src/content/dependencies/generateExternalIcon.js b/src/content/dependencies/generateExternalIcon.js index 637af658..03af643e 100644 --- a/src/content/dependencies/generateExternalIcon.js +++ b/src/content/dependencies/generateExternalIcon.js @@ -1,8 +1,6 @@ import {isExternalLinkContext} from '#external-links'; export default { - extraDependencies: ['html', 'language', 'to'], - data: (url) => ({url}), slots: { diff --git a/src/content/dependencies/generateExternalPlatform.js b/src/content/dependencies/generateExternalPlatform.js index c4f63ecf..b2822d64 100644 --- a/src/content/dependencies/generateExternalPlatform.js +++ b/src/content/dependencies/generateExternalPlatform.js @@ -1,8 +1,6 @@ import {isExternalLinkContext} from '#external-links'; export default { - extraDependencies: ['html', 'language'], - data: (url) => ({url}), slots: { diff --git a/src/content/dependencies/generateFlashActGalleryPage.js b/src/content/dependencies/generateFlashActGalleryPage.js index 84ab549d..896ee224 100644 --- a/src/content/dependencies/generateFlashActGalleryPage.js +++ b/src/content/dependencies/generateFlashActGalleryPage.js @@ -1,19 +1,6 @@ import striptags from 'striptags'; export default { - contentDependencies: [ - 'generateCoverGrid', - 'generateFlashActNavAccent', - 'generateFlashActSidebar', - 'generatePageLayout', - 'image', - 'linkFlash', - 'linkFlashAct', - 'linkFlashIndex', - ], - - extraDependencies: ['language'], - relations: (relation, act) => ({ layout: relation('generatePageLayout'), diff --git a/src/content/dependencies/generateFlashActNavAccent.js b/src/content/dependencies/generateFlashActNavAccent.js index c4ec77b8..7ad46051 100644 --- a/src/content/dependencies/generateFlashActNavAccent.js +++ b/src/content/dependencies/generateFlashActNavAccent.js @@ -1,15 +1,6 @@ import {atOffset} from '#sugar'; export default { - contentDependencies: [ - 'generateInterpageDotSwitcher', - 'generateNextLink', - 'generatePreviousLink', - 'linkFlashAct', - ], - - extraDependencies: ['wikiData'], - sprawl: ({flashActData}) => ({flashActData}), diff --git a/src/content/dependencies/generateFlashActSidebar.js b/src/content/dependencies/generateFlashActSidebar.js index 1421dde9..0d952077 100644 --- a/src/content/dependencies/generateFlashActSidebar.js +++ b/src/content/dependencies/generateFlashActSidebar.js @@ -1,10 +1,4 @@ export default { - contentDependencies: [ - 'generateFlashActSidebarCurrentActBox', - 'generateFlashActSidebarSideMapBox', - 'generatePageSidebar', - ], - relations: (relation, act, flash) => ({ sidebar: relation('generatePageSidebar'), diff --git a/src/content/dependencies/generateFlashActSidebarCurrentActBox.js b/src/content/dependencies/generateFlashActSidebarCurrentActBox.js index 6d152c7c..e08582fe 100644 --- a/src/content/dependencies/generateFlashActSidebarCurrentActBox.js +++ b/src/content/dependencies/generateFlashActSidebarCurrentActBox.js @@ -1,12 +1,4 @@ export default { - contentDependencies: [ - 'generatePageSidebarBox', - 'linkFlash', - 'linkFlashAct', - ], - - extraDependencies: ['html', 'language'], - relations: (relation, act, _flash) => ({ box: relation('generatePageSidebarBox'), diff --git a/src/content/dependencies/generateFlashActSidebarSideMapBox.js b/src/content/dependencies/generateFlashActSidebarSideMapBox.js index 7b26ef31..4b97f21d 100644 --- a/src/content/dependencies/generateFlashActSidebarSideMapBox.js +++ b/src/content/dependencies/generateFlashActSidebarSideMapBox.js @@ -1,15 +1,6 @@ import {stitchArrays} from '#sugar'; export default { - contentDependencies: [ - 'generateColorStyleAttribute', - 'generatePageSidebarBox', - 'linkFlashAct', - 'linkFlashIndex', - ], - - extraDependencies: ['html', 'wikiData'], - sprawl: ({flashSideData}) => ({flashSideData}), relations: (relation, sprawl, _act, _flash) => ({ diff --git a/src/content/dependencies/generateFlashArtworkColumn.js b/src/content/dependencies/generateFlashArtworkColumn.js index 5987df9e..207c3bf3 100644 --- a/src/content/dependencies/generateFlashArtworkColumn.js +++ b/src/content/dependencies/generateFlashArtworkColumn.js @@ -1,6 +1,4 @@ export default { - contentDependencies: ['generateCoverArtwork'], - relations: (relation, flash) => ({ coverArtwork: relation('generateCoverArtwork', flash.coverArtwork), diff --git a/src/content/dependencies/generateFlashIndexPage.js b/src/content/dependencies/generateFlashIndexPage.js index 2788406c..1fb286c6 100644 --- a/src/content/dependencies/generateFlashIndexPage.js +++ b/src/content/dependencies/generateFlashIndexPage.js @@ -1,17 +1,6 @@ import {stitchArrays} from '#sugar'; export default { - contentDependencies: [ - 'generateColorStyleAttribute', - 'generateCoverGrid', - 'generatePageLayout', - 'image', - 'linkFlash', - 'linkFlashAct', - ], - - extraDependencies: ['html', 'language', 'wikiData'], - sprawl: ({flashActData}) => ({flashActData}), query(sprawl) { diff --git a/src/content/dependencies/generateFlashInfoPage.js b/src/content/dependencies/generateFlashInfoPage.js index 095e43c4..935ffdc6 100644 --- a/src/content/dependencies/generateFlashInfoPage.js +++ b/src/content/dependencies/generateFlashInfoPage.js @@ -1,22 +1,19 @@ import {empty} from '#sugar'; -export default { - contentDependencies: [ - 'generateAdditionalNamesBox', - 'generateCommentaryEntry', - 'generateContentHeading', - 'generateContributionList', - 'generateFlashActSidebar', - 'generateFlashArtworkColumn', - 'generateFlashNavAccent', - 'generatePageLayout', - 'generateTrackList', - 'linkExternal', - 'linkFlashAct', - ], - - extraDependencies: ['html', 'language'], +function checkInterrupted(which, relations, {html}) { + if ( + !html.isBlank(relations.contributorContributionList) || + !html.isBlank(relations.featuredTracksList) + ) return true; + + if (which === 'crediting-sources') { + if (!html.isBlank(relations.artistCommentaryEntries)) return true; + } + + return false; +} +export default { query(flash) { const query = {}; @@ -53,6 +50,12 @@ export default { contentHeading: relation('generateContentHeading'), + commentaryContentHeading: + relation('generateCommentaryContentHeading', flash), + + readCommentaryLine: + relation('generateReadCommentaryLine', flash), + flashActLink: relation('linkFlashAct', flash.act), @@ -60,7 +63,7 @@ export default { relation('generateFlashNavAccent', flash), featuredTracksList: - relation('generateTrackList', flash.featuredTracks), + relation('generateTrackList', flash.featuredTracks, []), contributorContributionList: relation('generateContributionList', flash.contributorContribs), @@ -69,9 +72,10 @@ export default { flash.commentary .map(entry => relation('generateCommentaryEntry', entry)), - creditSourceEntries: - flash.commentary - .map(entry => relation('generateCommentaryEntry', entry)), + creditingSourcesSection: + relation('generateCollapsedContentEntrySection', + flash.creditingSources, + flash), }), data: (_query, flash) => ({ @@ -123,21 +127,16 @@ export default { {[html.joinChildren]: html.tag('br')}, language.encapsulate('releaseInfo', capsule => [ - !html.isBlank(relations.artistCommentaryEntries) && - language.encapsulate(capsule, 'readCommentary', capsule => - language.$(capsule, { - link: - html.tag('a', - {href: '#artist-commentary'}, - language.$(capsule, 'link')), - })), + checkInterrupted('commentary', relations, {html}) && + relations.readCommentaryLine, - !html.isBlank(relations.creditSourceEntries) && - language.encapsulate(capsule, 'readCreditSources', capsule => + checkInterrupted('crediting-sources', relations, {html}) && + !html.isBlank(relations.creditingSourcesSection) && + language.encapsulate(capsule, 'readCreditingSources', capsule => language.$(capsule, { link: html.tag('a', - {href: '#credit-sources'}, + {href: '#crediting-sources'}, language.$(capsule, 'link')), })), ])), @@ -168,24 +167,14 @@ export default { ]), html.tags([ - relations.contentHeading.clone() - .slots({ - attributes: {id: 'artist-commentary'}, - title: language.$('misc.artistCommentary'), - }), - + relations.commentaryContentHeading, relations.artistCommentaryEntries, ]), - html.tags([ - relations.contentHeading.clone() - .slots({ - attributes: {id: 'credit-sources'}, - title: language.$('misc.creditSources'), - }), - - relations.creditSourceEntries, - ]), + relations.creditingSourcesSection.slots({ + id: 'crediting-sources', + string: 'misc.creditingSources', + }), ], navLinkStyle: 'hierarchical', diff --git a/src/content/dependencies/generateFlashNavAccent.js b/src/content/dependencies/generateFlashNavAccent.js index 0f5d2d6b..db9d3c1e 100644 --- a/src/content/dependencies/generateFlashNavAccent.js +++ b/src/content/dependencies/generateFlashNavAccent.js @@ -1,15 +1,6 @@ import {atOffset} from '#sugar'; export default { - contentDependencies: [ - 'generateInterpageDotSwitcher', - 'generateNextLink', - 'generatePreviousLink', - 'linkFlash', - ], - - extraDependencies: ['html', 'language', 'wikiData'], - sprawl: ({flashActData}) => ({flashActData}), diff --git a/src/content/dependencies/generateFooterLocalizationLinks.js b/src/content/dependencies/generateFooterLocalizationLinks.js index dfd83aef..efa1972a 100644 --- a/src/content/dependencies/generateFooterLocalizationLinks.js +++ b/src/content/dependencies/generateFooterLocalizationLinks.js @@ -2,15 +2,6 @@ import {sortByName} from '#sort'; import {stitchArrays} from '#sugar'; export default { - extraDependencies: [ - 'defaultLanguage', - 'html', - 'language', - 'languages', - 'pagePath', - 'to', - ], - generate({ defaultLanguage, html, diff --git a/src/content/dependencies/generateGridActionLinks.js b/src/content/dependencies/generateGridActionLinks.js index 585a02b9..5b3f9c1e 100644 --- a/src/content/dependencies/generateGridActionLinks.js +++ b/src/content/dependencies/generateGridActionLinks.js @@ -1,6 +1,4 @@ export default { - extraDependencies: ['html'], - slots: { actionLinks: {validate: v => v.sparseArrayOf(v.isHTML)}, }, diff --git a/src/content/dependencies/generateGridExpando.js b/src/content/dependencies/generateGridExpando.js new file mode 100644 index 00000000..5a0cbce5 --- /dev/null +++ b/src/content/dependencies/generateGridExpando.js @@ -0,0 +1,37 @@ +export default { + slots: { + caption: {type: 'html', mutable: false}, + }, + + generate: (slots, {html, language}) => + language.encapsulate('misc.coverGrid', capsule => + html.tag('div', {class: 'grid-expando'}, + {[html.onlyIfSiblings]: true}, + + html.tag('p', {class: 'grid-expando-content'}, + {[html.joinChildren]: html.tag('br')}, + + [ + html.tag('span', {class: 'grid-caption'}, + slots.caption), + + !html.isBlank(slots.contentBelowCut) && + language.$(capsule, 'expandCollapseCue', { + cue: + html.tag('a', {class: 'grid-expando-toggle'}, + {href: '#'}, + + {[html.joinChildren]: ''}, + {[html.noEdgeWhitespace]: true}, + + [ + html.tag('span', {class: 'grid-expand-cue'}, + language.$(capsule, 'expand')), + + html.tag('span', {class: 'grid-collapse-cue'}, + {style: 'display: none'}, + language.$(capsule, 'collapse')), + ]), + }), + ]))), +}; diff --git a/src/content/dependencies/generateGroupGalleryPage.js b/src/content/dependencies/generateGroupGalleryPage.js index d51366ca..e378f8a2 100644 --- a/src/content/dependencies/generateGroupGalleryPage.js +++ b/src/content/dependencies/generateGroupGalleryPage.js @@ -1,99 +1,77 @@ import {sortChronologically} from '#sort'; -import {empty, stitchArrays} from '#sugar'; import {filterItemsForCarousel, getTotalDuration} from '#wiki-data'; export default { - contentDependencies: [ - 'generateCoverCarousel', - 'generateCoverGrid', - 'generateGroupNavLinks', - 'generateGroupSecondaryNav', - 'generateGroupSidebar', - 'generatePageLayout', - 'generateQuickDescription', - 'image', - 'linkAlbum', - 'linkListing', - ], - - extraDependencies: ['html', 'language', 'wikiData'], - sprawl: ({wikiInfo}) => ({enableGroupUI: wikiInfo.enableGroupUI}), - relations(relation, sprawl, group) { - const relations = {}; + query(_sprawl, group) { + const query = {}; - const albums = + query.allAlbums = sortChronologically(group.albums.slice(), {latestFirst: true}); - relations.layout = - relation('generatePageLayout'); - - relations.navLinks = - relation('generateGroupNavLinks', group); + query.allTracks = + query.allAlbums.flatMap((album) => album.tracks); - if (sprawl.enableGroupUI) { - relations.secondaryNav = - relation('generateGroupSecondaryNav', group); + query.carouselAlbums = + filterItemsForCarousel(group.featuredAlbums); - relations.sidebar = - relation('generateGroupSidebar', group); - } + return query; + }, - const carouselAlbums = filterItemsForCarousel(group.featuredAlbums); + relations: (relation, query, sprawl, group) => ({ + layout: + relation('generatePageLayout'), - if (!empty(carouselAlbums)) { - relations.coverCarousel = - relation('generateCoverCarousel'); + navLinks: + relation('generateGroupNavLinks', group), - relations.carouselLinks = - carouselAlbums - .map(album => relation('linkAlbum', album)); + secondaryNav: + (sprawl.enableGroupUI + ? relation('generateGroupSecondaryNav', group) + : null), - relations.carouselImages = - carouselAlbums - .map(album => relation('image', album.coverArtworks[0])); - } + coverCarousel: + relation('generateCoverCarousel'), - relations.quickDescription = - relation('generateQuickDescription', group); + carouselLinks: + query.carouselAlbums + .map(album => relation('linkAlbum', album)), - relations.coverGrid = - relation('generateCoverGrid'); + carouselImages: + query.carouselAlbums + .map(album => relation('image', album.coverArtworks[0])), - relations.gridLinks = - albums - .map(album => relation('linkAlbum', album)); + quickDescription: + relation('generateQuickDescription', group), - relations.gridImages = - albums.map(album => - (album.hasCoverArt - ? relation('image', album.coverArtworks[0]) - : relation('image'))); + albumViewSwitcher: + relation('generateIntrapageDotSwitcher'), - return relations; - }, + albumsBySeriesView: + relation('generateGroupGalleryPageAlbumsBySeriesView', group), - data(sprawl, group) { - const data = {}; + albumsByDateView: + relation('generateGroupGalleryPageAlbumsByDateView', group), + }), - data.name = group.name; - data.color = group.color; + data: (query, _sprawl, group) => ({ + name: + group.name, - const albums = sortChronologically(group.albums.slice(), {latestFirst: true}); - const tracks = albums.flatMap((album) => album.tracks); + color: + group.color, - data.numAlbums = albums.length; - data.numTracks = tracks.length; - data.totalDuration = getTotalDuration(tracks, {mainReleasesOnly: true}); + numAlbums: + query.allAlbums.length, - data.gridNames = albums.map(album => album.name); - data.gridDurations = albums.map(album => getTotalDuration(album.tracks)); - data.gridNumTracks = albums.map(album => album.tracks.length); + numTracks: + query.allTracks.length, - return data; - }, + totalDuration: + getTotalDuration(query.allTracks, {mainReleasesOnly: true}), + }), generate: (data, relations, {html, language}) => language.encapsulate('groupGalleryPage', pageCapsule => @@ -105,11 +83,10 @@ export default { mainClasses: ['top-index'], mainContent: [ - relations.coverCarousel - ?.slots({ - links: relations.carouselLinks, - images: relations.carouselImages, - }), + relations.coverCarousel.slots({ + links: relations.carouselLinks, + images: relations.carouselImages, + }), relations.quickDescription, @@ -134,49 +111,81 @@ export default { })), })), - relations.coverGrid - .slots({ - links: relations.gridLinks, - names: data.gridNames, - - images: - stitchArrays({ - image: relations.gridImages, - name: data.gridNames, - }).map(({image, name}) => - image.slots({ - missingSourceContent: - language.$('misc.coverGrid.noCoverArt', { - album: name, - }), - })), - - info: - stitchArrays({ - numTracks: data.gridNumTracks, - duration: data.gridDurations, - }).map(({numTracks, duration}) => - language.$('misc.coverGrid.details.albumLength', { - tracks: language.countTracks(numTracks, {unit: true}), - time: language.formatDuration(duration), - })), - }), + ([ + !html.isBlank(relations.albumsBySeriesView), + !html.isBlank(relations.albumsByDateView) + ]).filter(Boolean).length > 1 && + + language.encapsulate(pageCapsule, 'albumViewSwitcher', capsule => + html.tag('p', {class: 'gallery-view-switcher'}, + {class: ['drop', 'shiny']}, + + {[html.onlyIfContent]: true}, + {[html.joinChildren]: html.tag('br')}, + + [ + language.$(capsule), + + relations.albumViewSwitcher.slots({ + initialOptionIndex: 0, + + titles: [ + !html.isBlank(relations.albumsByDateView) && + language.$(capsule, 'byDate'), + + !html.isBlank(relations.albumsBySeriesView) && + language.$(capsule, 'bySeries'), + ].filter(Boolean), + + targetIDs: [ + !html.isBlank(relations.albumsByDateView) && + 'group-album-gallery-by-date', + + !html.isBlank(relations.albumsBySeriesView) && + 'group-album-gallery-by-series', + ].filter(Boolean), + }), + ])), + + /* + data.trackGridLabels.some(value => value !== null) && + html.tag('p', {class: 'gallery-set-switcher'}, + language.encapsulate(pageCapsule, 'setSwitcher', switcherCapsule => + language.$(switcherCapsule, { + sets: + relations.setSwitcher.slots({ + initialOptionIndex: 0, + + titles: + data.trackGridLabels.map(label => + label ?? + language.$(switcherCapsule, 'unlabeledSet')), + + targetIDs: + data.trackGridIDs, + }), + }))), + */ + + relations.albumsByDateView.slots({ + showTitle: + !html.isBlank(relations.albumsBySeriesView), + }), + + relations.albumsBySeriesView.slots({ + attributes: [ + !html.isBlank(relations.albumsBySeriesView) && + {style: 'display: none'}, + ], + }), ], - leftSidebar: - (relations.sidebar - ? relations.sidebar - .slot('currentExtra', 'gallery') - .content /* TODO: Kludge. */ - : null), - navLinkStyle: 'hierarchical', navLinks: relations.navLinks .slot('currentExtra', 'gallery') .content, - secondaryNav: - relations.secondaryNav ?? null, + secondaryNav: relations.secondaryNav, })), }; diff --git a/src/content/dependencies/generateGroupGalleryPageAlbumGrid.js b/src/content/dependencies/generateGroupGalleryPageAlbumGrid.js new file mode 100644 index 00000000..37c1951d --- /dev/null +++ b/src/content/dependencies/generateGroupGalleryPageAlbumGrid.js @@ -0,0 +1,97 @@ +import {stitchArrays} from '#sugar'; +import {getTotalDuration} from '#wiki-data'; + +export default { + query: (albums, _group) => ({ + artworks: + albums.map(album => + (album.hasCoverArt + ? album.coverArtworks[0] + : null)), + }), + + relations: (relation, query, albums, group) => ({ + coverGrid: + relation('generateCoverGrid'), + + links: + albums + .map(album => relation('linkAlbum', album)), + + images: + query.artworks + .map(artwork => relation('image', artwork)), + + tabs: + albums + .map(album => + relation('generateGroupGalleryPageAlbumGridTab', album, group)), + }), + + data: (query, albums, group) => ({ + names: + albums.map(album => album.name), + + styles: + albums.map(album => album.style), + + tracks: + albums.map(album => album.tracks.length), + + allWarnings: + query.artworks.flatMap(artwork => artwork?.contentWarnings), + + durations: + albums.map(album => + (album.hideDuration + ? null + : getTotalDuration(album.tracks))), + + notFromThisGroup: + albums.map(album => !album.groups.includes(group)), + }), + + generate: (data, relations, {language}) => + language.encapsulate('misc.coverGrid', capsule => + relations.coverGrid.slots({ + links: relations.links, + names: data.names, + notFromThisGroup: data.notFromThisGroup, + + images: + stitchArrays({ + image: relations.images, + name: data.names, + }).map(({image, name}) => + image.slots({ + missingSourceContent: + language.$(capsule, 'noCoverArt', { + album: name, + }), + })), + + itemAttributes: + data.styles.map(style => ({'data-style': style})), + + tab: relations.tabs, + + info: + stitchArrays({ + style: data.styles, + tracks: data.tracks, + duration: data.durations, + }).map(({style, tracks, duration}) => + (style === 'single' && duration + ? language.$(capsule, 'details.albumLength.single', { + time: language.formatDuration(duration), + }) + : duration + ? language.$(capsule, 'details.albumLength', { + tracks: language.countTracks(tracks, {unit: true}), + time: language.formatDuration(duration), + }) + : null)), + + revealAllWarnings: data.allWarnings, + })), +}; diff --git a/src/content/dependencies/generateGroupGalleryPageAlbumGridTab.js b/src/content/dependencies/generateGroupGalleryPageAlbumGridTab.js new file mode 100644 index 00000000..c3b860e4 --- /dev/null +++ b/src/content/dependencies/generateGroupGalleryPageAlbumGridTab.js @@ -0,0 +1,79 @@ +import {empty} from '#sugar'; + +export default { + query(album, group) { + if (album.groups.length > 1) { + const contextGroup = group; + + const candidateGroupCategory = + album.groups + .filter(group => !group.excludeFromGalleryTabs) + .find(group => group.category !== contextGroup.category) + ?.category ?? + null; + + const candidateGroups = + album.groups + .filter(group => !group.excludeFromGalleryTabs) + .filter(group => group.category === candidateGroupCategory); + + if (!empty(candidateGroups)) { + return { + mode: 'groups', + notedGroups: candidateGroups, + }; + } + } + + if (!empty(album.artistContribs)) { + if ( + album.artistContribs.length === 1 && + !empty(group.closelyLinkedArtists) && + (album.artistContribs[0].artist.name === + group.closelyLinkedArtists[0].artist.name) + ) { + return {mode: null}; + } + + return { + mode: 'artists', + notedArtistContribs: album.artistContribs, + }; + } + + return {mode: null};; + }, + + relations: (relation, query, _album, _group) => ({ + artistCredit: + (query.mode === 'artists' + ? relation('generateArtistCredit', query.notedArtistContribs, []) + : null), + }), + + data: (query, _album, _group) => ({ + mode: query.mode, + + groupNames: + (query.mode === 'groups' + ? query.notedGroups.map(group => group.name) + : null), + }), + + generate: (data, relations, {language}) => + language.encapsulate('misc.coverGrid.tab', capsule => + (data.mode === 'groups' + ? language.$(capsule, 'groups', { + groups: + language.formatUnitList(data.groupNames), + }) + : data.mode === 'artists' + ? relations.artistCredit.slots({ + normalStringKey: + capsule + '.artists', + + normalFeaturingStringKey: + capsule + '.artists.featuring', + }) + : null)), +}; diff --git a/src/content/dependencies/generateGroupGalleryPageAlbumsByDateView.js b/src/content/dependencies/generateGroupGalleryPageAlbumsByDateView.js new file mode 100644 index 00000000..75ef1048 --- /dev/null +++ b/src/content/dependencies/generateGroupGalleryPageAlbumsByDateView.js @@ -0,0 +1,48 @@ +import {sortChronologically} from '#sort'; + +export default { + query: (group) => ({ + albums: + sortChronologically(group.albums.slice(), {latestFirst: true}), + }), + + relations: (relation, query, group) => ({ + styleSelector: + (group.divideAlbumsByStyle + ? relation('generateGroupGalleryPageStyleSelector', group) + : null), + + albumGrid: + relation('generateGroupGalleryPageAlbumGrid', + query.albums, + group), + }), + + slots: { + showTitle: { + type: 'boolean', + }, + + attributes: { + type: 'attributes', + mutable: false, + }, + }, + + generate: (relations, slots, {html, language}) => + language.encapsulate('groupGalleryPage.albumsByDate', capsule => + html.tag('div', {id: 'group-album-gallery-by-date'}, + slots.attributes, + + {[html.onlyIfContent]: true}, + + html.tag('section', [ + slots.showTitle && + html.tag('h2', + language.$(capsule, 'title')), + + relations.styleSelector, + + relations.albumGrid, + ]))), +}; diff --git a/src/content/dependencies/generateGroupGalleryPageAlbumsBySeriesView.js b/src/content/dependencies/generateGroupGalleryPageAlbumsBySeriesView.js new file mode 100644 index 00000000..68cf249f --- /dev/null +++ b/src/content/dependencies/generateGroupGalleryPageAlbumsBySeriesView.js @@ -0,0 +1,23 @@ +export default { + relations: (relation, group) => ({ + seriesSections: + group.serieses + .map(series => + relation('generateGroupGalleryPageSeriesSection', series)), + }), + + slots: { + attributes: { + type: 'attributes', + mutable: false, + }, + }, + + generate: (relations, slots, {html}) => + html.tag('div', {id: 'group-album-gallery-by-series'}, + slots.attributes, + + {[html.onlyIfContent]: true}, + + relations.seriesSections), +}; diff --git a/src/content/dependencies/generateGroupGalleryPageSeriesSection.js b/src/content/dependencies/generateGroupGalleryPageSeriesSection.js new file mode 100644 index 00000000..1aa835d6 --- /dev/null +++ b/src/content/dependencies/generateGroupGalleryPageSeriesSection.js @@ -0,0 +1,138 @@ +import {sortChronologically} from '#sort'; + +export default { + query(series) { + const query = {}; + + query.albums = + sortChronologically(series.albums.slice(), {latestFirst: true}); + + query.allAlbumsDated = + series.albums.every(album => album.date); + + query.anyAlbumNotFromThisGroup = + series.albums.some(album => !album.groups.includes(series.group)); + + query.latestAlbum = + query.albums + .filter(album => album.date) + .at(0) ?? + null; + + query.earliestAlbum = + query.albums + .filter(album => album.date) + .at(-1) ?? + null; + + return query; + }, + + relations: (relation, query, series) => ({ + contentHeading: + relation('generateContentHeading'), + + grid: + relation('generateGroupGalleryPageAlbumGrid', + query.albums, + series.group), + }), + + data: (query, series) => ({ + name: + series.name, + + groupName: + series.group.name, + + albums: + series.albums.length, + + tracks: + series.albums + .flatMap(album => album.tracks) + .length, + + allAlbumsDated: + query.allAlbumsDated, + + anyAlbumNotFromThisGroup: + query.anyAlbumNotFromThisGroup, + + earliestAlbumDate: + (query.earliestAlbum + ? query.earliestAlbum.date + : null), + + latestAlbumDate: + (query.latestAlbum + ? query.latestAlbum.date + : null), + }), + + generate: (data, relations, {html, language}) => + language.encapsulate('groupGalleryPage.albumSection', capsule => + html.tags([ + relations.contentHeading.slots({ + tag: 'h2', + title: language.sanitize(data.name), + }), + + relations.grid.slots({ + cutIndex: 4, + + bottomCaption: + language.encapsulate(capsule, 'caption', captionCapsule => + html.tags([ + data.anyAlbumNotFromThisGroup && + language.$(captionCapsule, 'seriesAlbumsNotFromGroup', { + marker: + language.$('misc.coverGrid.details.notFromThisGroup.marker'), + + series: + html.tag('i', data.name), + + group: data.groupName, + }), + + language.encapsulate(captionCapsule, workingCapsule => { + const workingOptions = {}; + + workingOptions.tracks = + html.tag('b', + language.countTracks(data.tracks, {unit: true})); + + workingOptions.albums = + html.tag('b', + language.countAlbums(data.albums, {unit: true})); + + if (data.allAlbumsDated) { + const earliestDate = data.earliestAlbumDate; + const latestDate = data.latestAlbumDate; + + const earliestYear = earliestDate.getFullYear(); + const latestYear = latestDate.getFullYear(); + + if (earliestYear === latestYear) { + if (data.albums === 1) { + workingCapsule += '.withDate'; + workingOptions.date = + language.formatDate(earliestDate); + } else { + workingCapsule += '.withYear'; + workingOptions.year = + language.formatYear(earliestDate); + } + } else { + workingCapsule += '.withYearRange'; + workingOptions.yearRange = + language.formatYearRange(earliestDate, latestDate); + } + } + + return language.$(workingCapsule, workingOptions); + }), + ], {[html.joinChildren]: html.tag('br')})), + }), + ])), +}; diff --git a/src/content/dependencies/generateGroupGalleryPageStyleSelector.js b/src/content/dependencies/generateGroupGalleryPageStyleSelector.js new file mode 100644 index 00000000..9342e50f --- /dev/null +++ b/src/content/dependencies/generateGroupGalleryPageStyleSelector.js @@ -0,0 +1,60 @@ +import {unique} from '#sugar'; + +export default { + query: (group) => ({ + styles: + unique(group.albums.map(album => album.style)), + }), + + data: (query, group) => ({ + albums: + group.albums.length, + + styles: + query.styles, + }), + + generate: (data, {html, language}) => + language.encapsulate('groupGalleryPage', pageCapsule => + (data.styles.length <= 1 + ? html.blank() + : html.tag('p', {class: 'gallery-style-selector'}, + {class: ['drop', 'shiny']}, + + language.encapsulate(pageCapsule, 'albumStyleSwitcher', capsule => [ + html.tag('span', + language.$(capsule)), + + html.tag('br'), + + html.tag('span', {class: 'styles'}, + data.styles.map(style => + html.tag('label', {'data-style': style}, [ + html.tag('input', {type: 'checkbox'}, + {checked: true}), + + html.tag('span', + language.$(capsule, style)), + ]))), + + html.tag('br'), + + html.tag('span', {class: ['count', 'all']}, + language.$(capsule, 'count.all', { + total: data.albums, + })), + + html.tag('span', {class: ['count', 'filtered']}, + {style: 'display: none'}, + + language.$(capsule, 'count.filtered', { + count: html.tag('span'), + total: data.albums, + })), + + html.tag('span', {class: ['count', 'none']}, + {style: 'display: none'}, + + language.$(capsule, 'count.none')), + ])))), +}; diff --git a/src/content/dependencies/generateGroupInfoPage.js b/src/content/dependencies/generateGroupInfoPage.js index 7b9c2afa..0f3093b2 100644 --- a/src/content/dependencies/generateGroupInfoPage.js +++ b/src/content/dependencies/generateGroupInfoPage.js @@ -1,20 +1,6 @@ import {stitchArrays} from '#sugar'; export default { - contentDependencies: [ - 'generateColorStyleAttribute', - 'generateGroupInfoPageAlbumsSection', - 'generateGroupNavLinks', - 'generateGroupSecondaryNav', - 'generateGroupSidebar', - 'generatePageLayout', - 'linkArtist', - 'linkExternal', - 'transformContent', - ], - - extraDependencies: ['html', 'language', 'wikiData'], - sprawl: ({wikiInfo}) => ({ enableGroupUI: wikiInfo.enableGroupUI, diff --git a/src/content/dependencies/generateGroupInfoPageAlbumsListByDate.js b/src/content/dependencies/generateGroupInfoPageAlbumsListByDate.js index df42598d..de55f33a 100644 --- a/src/content/dependencies/generateGroupInfoPageAlbumsListByDate.js +++ b/src/content/dependencies/generateGroupInfoPageAlbumsListByDate.js @@ -1,10 +1,6 @@ import {sortChronologically} from '#sort'; export default { - contentDependencies: ['generateGroupInfoPageAlbumsListItem'], - - extraDependencies: ['html'], - query: (group) => ({ // Typically, a latestFirst: false (default) chronological sort would be // appropriate here, but navigation between adjacent albums in a group is a diff --git a/src/content/dependencies/generateGroupInfoPageAlbumsListBySeries.js b/src/content/dependencies/generateGroupInfoPageAlbumsListBySeries.js index bcd5d288..f8314d71 100644 --- a/src/content/dependencies/generateGroupInfoPageAlbumsListBySeries.js +++ b/src/content/dependencies/generateGroupInfoPageAlbumsListBySeries.js @@ -1,13 +1,6 @@ import {stitchArrays} from '#sugar'; export default { - contentDependencies: [ - 'generateContentHeading', - 'generateGroupInfoPageAlbumsListItem', - ], - - extraDependencies: ['html', 'language'], - query: (group) => ({ closelyLinkedArtists: group.closelyLinkedArtists diff --git a/src/content/dependencies/generateGroupInfoPageAlbumsListItem.js b/src/content/dependencies/generateGroupInfoPageAlbumsListItem.js index 4680cb46..1211dfb8 100644 --- a/src/content/dependencies/generateGroupInfoPageAlbumsListItem.js +++ b/src/content/dependencies/generateGroupInfoPageAlbumsListItem.js @@ -1,16 +1,6 @@ import {empty} from '#sugar'; export default { - contentDependencies: [ - 'generateAbsoluteDatetimestamp', - 'generateArtistCredit', - 'generateColorStyleAttribute', - 'linkAlbum', - 'linkGroup', - ], - - extraDependencies: ['html', 'language'], - query: (album, group) => { const otherCategory = album.groups @@ -76,7 +66,7 @@ export default { workingOptions.yearAccent = language.$(yearCapsule, 'accent', { year: - relations.datetimestamp.slots({style: 'year', tooltip: true}), + relations.datetimestamp.slot('style', 'year'), }); } @@ -127,9 +117,7 @@ export default { workingCapsule += '.withArtists'; workingOptions.by = html.tag('span', {class: 'by'}, - // TODO: This is obviously evil. - html.metatag('chunkwrap', {split: /,| (?=and)/}, - html.resolve(artistCredit))); + artistCredit); } return language.$(workingCapsule, workingOptions); diff --git a/src/content/dependencies/generateGroupInfoPageAlbumsSection.js b/src/content/dependencies/generateGroupInfoPageAlbumsSection.js index 0b678e9d..4470eb2f 100644 --- a/src/content/dependencies/generateGroupInfoPageAlbumsSection.js +++ b/src/content/dependencies/generateGroupInfoPageAlbumsSection.js @@ -1,14 +1,4 @@ export default { - contentDependencies: [ - 'generateContentHeading', - 'generateGroupInfoPageAlbumsListByDate', - 'generateGroupInfoPageAlbumsListBySeries', - 'generateIntrapageDotSwitcher', - 'linkGroupGallery', - ], - - extraDependencies: ['html', 'language'], - relations: (relation, group) => ({ contentHeading: relation('generateContentHeading'), diff --git a/src/content/dependencies/generateGroupNavAccent.js b/src/content/dependencies/generateGroupNavAccent.js index 0e4ebe8a..18281bf0 100644 --- a/src/content/dependencies/generateGroupNavAccent.js +++ b/src/content/dependencies/generateGroupNavAccent.js @@ -1,14 +1,6 @@ import {empty} from '#sugar'; export default { - contentDependencies: [ - 'generateInterpageDotSwitcher', - 'linkGroup', - 'linkGroupGallery', - ], - - extraDependencies: ['html', 'language'], - relations: (relation, group) => ({ switcher: relation('generateInterpageDotSwitcher'), diff --git a/src/content/dependencies/generateGroupNavLinks.js b/src/content/dependencies/generateGroupNavLinks.js index bdc3ee4c..4f13e474 100644 --- a/src/content/dependencies/generateGroupNavLinks.js +++ b/src/content/dependencies/generateGroupNavLinks.js @@ -1,7 +1,4 @@ export default { - contentDependencies: ['generateGroupNavAccent', 'linkGroup'], - extraDependencies: ['html', 'language', 'wikiData'], - sprawl: ({groupCategoryData, wikiInfo}) => ({ groupCategoryData, enableGroupUI: wikiInfo.enableGroupUI, diff --git a/src/content/dependencies/generateGroupSecondaryNav.js b/src/content/dependencies/generateGroupSecondaryNav.js index c48f3142..6b4347dd 100644 --- a/src/content/dependencies/generateGroupSecondaryNav.js +++ b/src/content/dependencies/generateGroupSecondaryNav.js @@ -1,9 +1,4 @@ export default { - contentDependencies: [ - 'generateSecondaryNav', - 'generateGroupSecondaryNavCategoryPart', - ], - relations: (relation, group) => ({ secondaryNav: relation('generateSecondaryNav'), diff --git a/src/content/dependencies/generateGroupSecondaryNavCategoryPart.js b/src/content/dependencies/generateGroupSecondaryNavCategoryPart.js index b2adb9f8..df627c99 100644 --- a/src/content/dependencies/generateGroupSecondaryNavCategoryPart.js +++ b/src/content/dependencies/generateGroupSecondaryNavCategoryPart.js @@ -1,15 +1,6 @@ import {atOffset} from '#sugar'; export default { - contentDependencies: [ - 'generateColorStyleAttribute', - 'generateSecondaryNavParentSiblingsPart', - 'linkGroupDynamically', - 'linkListing', - ], - - extraDependencies: ['html', 'language', 'wikiData'], - sprawl: ({listingSpec, wikiInfo}) => ({ groupsByCategoryListing: (wikiInfo.enableListings diff --git a/src/content/dependencies/generateGroupSidebar.js b/src/content/dependencies/generateGroupSidebar.js index 0888cbbe..1359eaca 100644 --- a/src/content/dependencies/generateGroupSidebar.js +++ b/src/content/dependencies/generateGroupSidebar.js @@ -1,12 +1,4 @@ export default { - contentDependencies: [ - 'generateGroupSidebarCategoryDetails', - 'generatePageSidebar', - 'generatePageSidebarBox', - ], - - extraDependencies: ['html', 'language', 'wikiData'], - sprawl: ({groupCategoryData}) => ({groupCategoryData}), relations: (relation, sprawl, group) => ({ diff --git a/src/content/dependencies/generateGroupSidebarCategoryDetails.js b/src/content/dependencies/generateGroupSidebarCategoryDetails.js index 208ccd07..a7e1f240 100644 --- a/src/content/dependencies/generateGroupSidebarCategoryDetails.js +++ b/src/content/dependencies/generateGroupSidebarCategoryDetails.js @@ -1,14 +1,6 @@ import {empty, stitchArrays} from '#sugar'; export default { - contentDependencies: [ - 'generateColorStyleAttribute', - 'linkGroup', - 'linkGroupGallery', - ], - - extraDependencies: ['html', 'language'], - relations(relation, category) { return { colorStyle: diff --git a/src/content/dependencies/generateImageOverlay.js b/src/content/dependencies/generateImageOverlay.js index cfb78a1b..006cfcce 100644 --- a/src/content/dependencies/generateImageOverlay.js +++ b/src/content/dependencies/generateImageOverlay.js @@ -1,6 +1,4 @@ export default { - extraDependencies: ['html', 'language'], - generate: ({html, language}) => html.tag('div', {id: 'image-overlay-container'}, html.tag('div', {id: 'image-overlay-content-container'}, [ diff --git a/src/content/dependencies/generateInterpageDotSwitcher.js b/src/content/dependencies/generateInterpageDotSwitcher.js index 5a33444e..ddb7cb37 100644 --- a/src/content/dependencies/generateInterpageDotSwitcher.js +++ b/src/content/dependencies/generateInterpageDotSwitcher.js @@ -1,7 +1,4 @@ export default { - contentDependencies: ['generateDotSwitcherTemplate'], - extraDependencies: ['html', 'language'], - relations: (relation) => ({ template: relation('generateDotSwitcherTemplate'), diff --git a/src/content/dependencies/generateIntrapageDotSwitcher.js b/src/content/dependencies/generateIntrapageDotSwitcher.js index 1d58367d..943d862c 100644 --- a/src/content/dependencies/generateIntrapageDotSwitcher.js +++ b/src/content/dependencies/generateIntrapageDotSwitcher.js @@ -1,9 +1,6 @@ import {stitchArrays} from '#sugar'; export default { - contentDependencies: ['generateDotSwitcherTemplate'], - extraDependencies: ['html', 'language'], - relations: (relation) => ({ template: relation('generateDotSwitcherTemplate'), @@ -39,11 +36,32 @@ export default { stitchArrays({ title: slots.titles, targetID: slots.targetIDs, - }).map(({title, targetID}) => - html.tag('a', {href: '#'}, - {'data-target-id': targetID}, - {[html.onlyIfContent]: true}, + }).map(({title, targetID}) => { + const {content} = html.smush(title); + + const customCue = + content.find(item => + item?.tagName === 'span' && + item.attributes.has('class', 'dot-switcher-interaction-cue')); + + const cue = + (customCue && !html.isBlank(customCue) + ? customCue.content + : language.sanitize(title)); + + const a = + html.tag('a', {href: '#'}, + {'data-target-id': targetID}, + {[html.onlyIfContent]: true}, + + cue); - language.sanitize(title))), + if (customCue) { + content.splice(content.indexOf(customCue), 1, a); + return html.tags(content, {[html.joinChildren]: ''}); + } else { + return a; + } + }), }), }; diff --git a/src/content/dependencies/generateListAllAdditionalFilesAlbumChunk.js b/src/content/dependencies/generateListAllAdditionalFilesAlbumChunk.js new file mode 100644 index 00000000..e381a745 --- /dev/null +++ b/src/content/dependencies/generateListAllAdditionalFilesAlbumChunk.js @@ -0,0 +1,19 @@ +export default { + relations: (relation, _album, additionalFiles) => ({ + chunk: + relation('generateListAllAdditionalFilesChunk', additionalFiles), + }), + + slots: { + stringsKey: {type: 'string'}, + }, + + generate: (relations, slots, {language}) => + language.encapsulate('listingPage', slots.stringsKey, pageCapsule => + relations.chunk.slots({ + title: + language.$(pageCapsule, 'albumFiles'), + + stringsKey: slots.stringsKey, + })), +}; diff --git a/src/content/dependencies/generateListAllAdditionalFilesAlbumSection.js b/src/content/dependencies/generateListAllAdditionalFilesAlbumSection.js new file mode 100644 index 00000000..0f14f12c --- /dev/null +++ b/src/content/dependencies/generateListAllAdditionalFilesAlbumSection.js @@ -0,0 +1,42 @@ +export default { + relations: (relation, album, property) => ({ + heading: + relation('generateContentHeading'), + + albumLink: + relation('linkAlbum', album), + + albumChunk: + relation('generateListAllAdditionalFilesAlbumChunk', + album, + album[property] ?? []), + + trackChunks: + album.tracks.map(track => + relation('generateListAllAdditionalFilesTrackChunk', + track, + track[property] ?? [])), + }), + + slots: { + stringsKey: {type: 'string'}, + }, + + generate: (relations, slots, {html}) => + html.tags([ + relations.heading.slots({ + tag: 'h3', + title: relations.albumLink, + }), + + html.tag('dl', + {[html.onlyIfContent]: true}, + + [ + relations.albumChunk.slot('stringsKey', slots.stringsKey), + + relations.trackChunks.map(trackChunk => + trackChunk.slot('stringsKey', slots.stringsKey)), + ]), + ]), +}; diff --git a/src/content/dependencies/generateListAllAdditionalFilesChunk.js b/src/content/dependencies/generateListAllAdditionalFilesChunk.js index deb8c4ea..d68e3bc1 100644 --- a/src/content/dependencies/generateListAllAdditionalFilesChunk.js +++ b/src/content/dependencies/generateListAllAdditionalFilesChunk.js @@ -1,7 +1,22 @@ -import {empty, stitchArrays} from '#sugar'; +import {stitchArrays} from '#sugar'; export default { - extraDependencies: ['html', 'language'], + relations: (relation, additionalFiles) => ({ + links: + additionalFiles + .map(file => file.filenames + .map(filename => relation('linkAdditionalFile', file, filename))), + }), + + data: (additionalFiles) => ({ + titles: + additionalFiles + .map(file => file.title), + + filenames: + additionalFiles + .map(file => file.filenames), + }), slots: { title: { @@ -9,82 +24,73 @@ export default { mutable: false, }, - additionalFileTitles: { - validate: v => v.strictArrayOf(v.isHTML), - }, - - additionalFileLinks: { - validate: v => v.strictArrayOf(v.strictArrayOf(v.isHTML)), - }, - - additionalFileFiles: { - validate: v => v.strictArrayOf(v.strictArrayOf(v.isString)), - }, - stringsKey: {type: 'string'}, }, - generate(slots, {html, language}) { - if (empty(slots.additionalFileLinks)) { - return html.blank(); - } + generate: (data, relations, slots, {html, language}) => + language.encapsulate('listingPage', slots.stringsKey, pageCapsule => + html.tags([ + html.tag('dt', + {[html.onlyIfSiblings]: true}, + slots.title), - return html.tags([ - html.tag('dt', slots.title), - html.tag('dd', - html.tag('ul', - stitchArrays({ - additionalFileTitle: slots.additionalFileTitles, - additionalFileLinks: slots.additionalFileLinks, - additionalFileFiles: slots.additionalFileFiles, - }).map(({ - additionalFileTitle, - additionalFileLinks, - additionalFileFiles, - }) => - language.encapsulate('listingPage', slots.stringsKey, 'file', capsule => - (additionalFileLinks.length === 1 - ? html.tag('li', - additionalFileLinks[0].slots({ - content: - language.$(capsule, { - title: additionalFileTitle, - }), - })) + html.tag('dd', + {[html.onlyIfContent]: true}, - : additionalFileLinks.length === 0 - ? html.tag('li', - language.$(capsule, 'withNoFiles', { - title: additionalFileTitle, - })) + html.tag('ul', + {[html.onlyIfContent]: true}, - : html.tag('li', {class: 'has-details'}, - html.tag('details', [ - html.tag('summary', - html.tag('span', - language.$(capsule, 'withMultipleFiles', { - title: - html.tag('b', additionalFileTitle), + stitchArrays({ + title: data.titles, + links: relations.links, + filenames: data.filenames, + }).map(({ + title, + links, + filenames, + }) => + language.encapsulate(pageCapsule, 'file', capsule => + (links.length === 1 + ? html.tag('li', + links[0].slots({ + content: + language.$(capsule, { + title: title, + }), + })) - files: - language.countAdditionalFiles( - additionalFileLinks.length, - {unit: true}), - }))), + : links.length === 0 + ? html.tag('li', + language.$(capsule, 'withNoFiles', { + title: title, + })) - html.tag('ul', - stitchArrays({ - additionalFileLink: additionalFileLinks, - additionalFileFile: additionalFileFiles, - }).map(({additionalFileLink, additionalFileFile}) => - html.tag('li', - additionalFileLink.slots({ - content: - language.$(capsule, { - title: additionalFileFile, - }), - })))), - ]))))))), - ]); - }, + : html.tag('li', {class: 'has-details'}, + html.tag('details', [ + html.tag('summary', + html.tag('span', + language.$(capsule, 'withMultipleFiles', { + title: + html.tag('b', title), + + files: + language.countAdditionalFiles( + links.length, + {unit: true}), + }))), + + html.tag('ul', + stitchArrays({ + link: links, + filename: filenames, + }).map(({link, filename}) => + html.tag('li', + link.slots({ + content: + language.$(capsule, { + title: filename, + }), + })))), + ]))))))), + ])), }; diff --git a/src/content/dependencies/generateListAllAdditionalFilesTrackChunk.js b/src/content/dependencies/generateListAllAdditionalFilesTrackChunk.js new file mode 100644 index 00000000..9ac79bb5 --- /dev/null +++ b/src/content/dependencies/generateListAllAdditionalFilesTrackChunk.js @@ -0,0 +1,20 @@ +export default { + relations: (relation, track, additionalFiles) => ({ + trackLink: + relation('linkTrack', track), + + chunk: + relation('generateListAllAdditionalFilesChunk', additionalFiles), + }), + + slots: { + stringsKey: {type: 'string'}, + }, + + generate: (relations, slots) => + relations.chunk.slots({ + title: relations.trackLink, + stringsKey: slots.stringsKey, + }), +}; + diff --git a/src/content/dependencies/generateListRandomPageLinksAlbumLink.js b/src/content/dependencies/generateListRandomPageLinksAlbumLink.js index b3560aca..29e7b1c9 100644 --- a/src/content/dependencies/generateListRandomPageLinksAlbumLink.js +++ b/src/content/dependencies/generateListRandomPageLinksAlbumLink.js @@ -1,6 +1,4 @@ export default { - contentDependencies: ['linkAlbum'], - data: (album) => ({directory: album.directory}), diff --git a/src/content/dependencies/generateListingIndexList.js b/src/content/dependencies/generateListingIndexList.js index 78622e6e..db494f37 100644 --- a/src/content/dependencies/generateListingIndexList.js +++ b/src/content/dependencies/generateListingIndexList.js @@ -1,9 +1,6 @@ import {empty, stitchArrays} from '#sugar'; export default { - contentDependencies: ['linkListing'], - extraDependencies: ['html', 'language', 'wikiData'], - sprawl({listingTargetSpec, wikiInfo}) { return {listingTargetSpec, wikiInfo}; }, diff --git a/src/content/dependencies/generateListingPage.js b/src/content/dependencies/generateListingPage.js index 5f9a99a9..987008eb 100644 --- a/src/content/dependencies/generateListingPage.js +++ b/src/content/dependencies/generateListingPage.js @@ -1,67 +1,36 @@ -import {bindOpts, empty, stitchArrays} from '#sugar'; +import {bindOpts, stitchArrays} from '#sugar'; export default { - contentDependencies: [ - 'generateContentHeading', - 'generateListingSidebar', - 'generatePageLayout', - 'linkListing', - 'linkListingIndex', - 'linkTemplate', - ], + relations: (relation, listing) => ({ + layout: + relation('generatePageLayout'), - extraDependencies: ['html', 'language', 'wikiData'], + sidebar: + relation('generateListingSidebar', listing), - relations(relation, listing) { - const relations = {}; + listingsIndexLink: + relation('linkListingIndex'), - relations.layout = - relation('generatePageLayout'); + chunkHeading: + relation('generateContentHeading'), - relations.sidebar = - relation('generateListingSidebar', listing); + showSkipToSectionLinkTemplate: + relation('linkTemplate'), - relations.listingsIndexLink = - relation('linkListingIndex'); + sameTargetListingsLine: + (listing.target.listings.length > 1 + ? relation('generateListingPageSameTargetListingsLine', listing) + : null), - relations.chunkHeading = - relation('generateContentHeading'); + seeAlsoLinks: + listing.seeAlso + .map(listing => relation('linkListing', listing)), + }), - relations.showSkipToSectionLinkTemplate = - relation('linkTemplate'); - - if (listing.target.listings.length > 1) { - relations.sameTargetListingLinks = - listing.target.listings - .map(listing => relation('linkListing', listing)); - } else { - relations.sameTargetListingLinks = []; - } - - relations.seeAlsoLinks = - (!empty(listing.seeAlso) - ? listing.seeAlso - .map(listing => relation('linkListing', listing)) - : []); - - return relations; - }, - - data(listing) { - return { - stringsKey: listing.stringsKey, - - targetStringsKey: listing.target.stringsKey, - - sameTargetListingStringsKeys: - listing.target.listings - .map(listing => listing.stringsKey), - - sameTargetListingsCurrentIndex: - listing.target.listings - .indexOf(listing), - }; - }, + data: (listing) => ({ + stringsKey: + listing.stringsKey, + }), slots: { type: { @@ -169,29 +138,7 @@ export default { headingMode: 'sticky', mainContent: [ - html.tag('p', - {[html.onlyIfContent]: true}, - language.$('listingPage.listingsFor', { - [language.onlyIfOptions]: ['listings'], - - target: - language.$('listingPage.target', data.targetStringsKey), - - listings: - language.formatUnitList( - stitchArrays({ - link: relations.sameTargetListingLinks, - stringsKey: data.sameTargetListingStringsKeys, - }).map(({link, stringsKey}, index) => - html.tag('span', - index === data.sameTargetListingsCurrentIndex && - {class: 'current'}, - - link.slots({ - attributes: {class: 'nowrap'}, - content: language.$('listingPage', stringsKey, 'title.short'), - })))), - })), + relations.sameTargetListingsLine, html.tag('p', {[html.onlyIfContent]: true}, diff --git a/src/content/dependencies/generateListingPageSameTargetListingsLine.js b/src/content/dependencies/generateListingPageSameTargetListingsLine.js new file mode 100644 index 00000000..2146b1eb --- /dev/null +++ b/src/content/dependencies/generateListingPageSameTargetListingsLine.js @@ -0,0 +1,46 @@ +import {stitchArrays} from '#sugar'; + +export default { + relations: (relation, listing) => ({ + listingLinks: + listing.target.listings + .map(listing => relation('linkListing', listing)), + }), + + data: (listing) => ({ + targetStringsKey: + listing.target.stringsKey, + + listingStringsKeys: + listing.target.listings.map(listing => listing.stringsKey), + + currentIndex: + listing.target.listings.indexOf(listing), + }), + + generate: (data, relations, {html, language}) => + html.tag('p', + {[html.onlyIfContent]: true}, + + language.$('listingPage.listingsFor', { + [language.onlyIfOptions]: ['listings'], + + target: + language.$('listingPage.target', data.targetStringsKey), + + listings: + language.formatUnitList( + stitchArrays({ + link: relations.listingLinks, + stringsKey: data.listingStringsKeys, + }).map(({link, stringsKey}, index) => + html.tag('span', + index === data.currentIndex && + {class: 'current'}, + + link.slots({ + attributes: {class: 'nowrap'}, + content: language.$('listingPage', stringsKey, 'title.short'), + })))), + })), +}; diff --git a/src/content/dependencies/generateListingSidebar.js b/src/content/dependencies/generateListingSidebar.js index aeac05cf..2d9429cf 100644 --- a/src/content/dependencies/generateListingSidebar.js +++ b/src/content/dependencies/generateListingSidebar.js @@ -1,13 +1,4 @@ export default { - contentDependencies: [ - 'generateListingIndexList', - 'generatePageSidebar', - 'generatePageSidebarBox', - 'linkListingIndex', - ], - - extraDependencies: ['html'], - relations: (relation, currentListing) => ({ sidebar: relation('generatePageSidebar'), diff --git a/src/content/dependencies/generateListingsIndexPage.js b/src/content/dependencies/generateListingsIndexPage.js index b57ebe15..80963d12 100644 --- a/src/content/dependencies/generateListingsIndexPage.js +++ b/src/content/dependencies/generateListingsIndexPage.js @@ -1,20 +1,14 @@ import {getTotalDuration} from '#wiki-data'; export default { - contentDependencies: [ - 'generateListingIndexList', - 'generateListingSidebar', - 'generatePageLayout', - ], - - extraDependencies: ['html', 'language', 'wikiData'], - sprawl({albumData, trackData, wikiInfo}) { return { wikiName: wikiInfo.name, numTracks: trackData.length, numAlbums: albumData.length, - totalDuration: getTotalDuration(trackData), + totalDuration: + getTotalDuration( + trackData.filter(track => track.countInArtistTotals)), }; }, diff --git a/src/content/dependencies/generateLyricsEntry.js b/src/content/dependencies/generateLyricsEntry.js index 4f9c22f1..15f84b27 100644 --- a/src/content/dependencies/generateLyricsEntry.js +++ b/src/content/dependencies/generateLyricsEntry.js @@ -1,13 +1,54 @@ export default { - contentDependencies: [ - 'transformContent', - ], - - extraDependencies: ['html', 'language'], - relations: (relation, entry) => ({ content: relation('transformContent', entry.body), + + artistText: + relation('transformContent', entry.artistText), + + artistLinks: + entry.artists + .filter(artist => artist.name !== 'HSMusic Wiki') // smh + .map(artist => relation('linkArtist', artist)), + + sourceLinks: + entry.sourceURLs + .map(url => relation('linkExternal', url)), + + originDetails: + relation('transformContent', entry.originDetails), + }), + + data: (entry) => ({ + isWikiLyrics: + entry.isWikiLyrics, + + hasSquareBracketAnnotations: + entry.hasSquareBracketAnnotations, + + numStanzas: + 1 + + + (Array.from( + entry.body + .matchAll(/\n\n|<br><br>/g)) + + .length) + + + (entry.body.includes('<br') + ? entry.body.split('\n').length + : 0), + + numLines: + 1 + + + (Array.from( + entry.body + .replaceAll(/(<br>){1,}/g, '\n') + .replaceAll(/\n{2,}/g, '\n') + .matchAll(/\n/g)) + + .length), }), slots: { @@ -17,9 +58,62 @@ export default { }, }, - generate: (relations, slots, {html}) => - html.tag('div', {class: 'lyrics-entry'}, - slots.attributes, + generate: (data, relations, slots, {html, language}) => + language.encapsulate('misc.lyrics', capsule => + html.tag('blockquote', {class: 'lyrics-entry'}, + slots.attributes, + + {'data-stanzas': data.numStanzas}, + {'data-lines': data.numLines}, + + (data.numStanzas > 1 || + data.numLines > 8) && + {class: 'long-lyrics'}, + + [ + html.tag('p', {class: 'lyrics-details'}, + {[html.onlyIfContent]: true}, + {[html.joinChildren]: html.tag('br')}, + + [ + language.$(capsule, 'source', { + [language.onlyIfOptions]: ['source'], + + source: + language.formatUnitList( + relations.sourceLinks.map(link => + link.slots({ + indicateExternal: true, + tab: 'separate', + }))), + }), + + data.isWikiLyrics && + language.$(capsule, 'contributors', { + [language.onlyIfOptions]: ['contributors'], + + contributors: + (html.isBlank(relations.artistText) + ? language.formatUnitList(relations.artistLinks) + : relations.artistText.slot('mode', 'inline')), + }), + + // This check is doubled up only for clarity: entries are coded + // in data so that `hasSquareBracketAnnotations` is only true + // if `isWikiLyrics` is also true. + data.isWikiLyrics && + data.hasSquareBracketAnnotations && + language.$(capsule, 'squareBracketAnnotations'), + ]), + + html.tag('p', {class: 'origin-details'}, + {[html.onlyIfContent]: true}, + + relations.originDetails.slots({ + mode: 'inline', + absorbPunctuationFollowingExternalLinks: false, + })), - relations.content.slot('mode', 'lyrics')), + relations.content.slot('mode', 'lyrics'), + ])), }; diff --git a/src/content/dependencies/generateLyricsSection.js b/src/content/dependencies/generateLyricsSection.js index f6b719a9..bbc3a776 100644 --- a/src/content/dependencies/generateLyricsSection.js +++ b/src/content/dependencies/generateLyricsSection.js @@ -1,15 +1,6 @@ import {stitchArrays} from '#sugar'; export default { - contentDependencies: [ - 'generateContentHeading', - 'generateIntrapageDotSwitcher', - 'generateLyricsEntry', - 'transformContent', - ], - - extraDependencies: ['html', 'language'], - relations: (relation, entries) => ({ heading: relation('generateContentHeading'), @@ -21,10 +12,10 @@ export default { entries .map(entry => relation('generateLyricsEntry', entry)), - annotations: + annotationParts: entries - .map(entry => entry.annotation) - .map(annotation => relation('transformContent', annotation)), + .map(entry => entry.annotationParts + .map(part => relation('transformContent', part))), }), data: (entries) => ({ @@ -54,11 +45,24 @@ export default { initialOptionIndex: 0, titles: - relations.annotations.map(annotation => - annotation.slots({ - mode: 'inline', - textOnly: true, - })), + relations.annotationParts + .map(([first, ...rest]) => + language.formatUnitList([ + html.tag('span', + {class: 'dot-switcher-interaction-cue'}, + {[html.onlyIfContent]: true}, + + first?.slots({ + mode: 'inline', + textOnly: true, + })), + + ...rest.map(part => + part.slots({ + mode: 'inline', + textOnly: true, + })), + ])), targetIDs: data.ids, diff --git a/src/content/dependencies/generateName.js b/src/content/dependencies/generateName.js new file mode 100644 index 00000000..e0d0c6d3 --- /dev/null +++ b/src/content/dependencies/generateName.js @@ -0,0 +1,33 @@ +export default { + contentDependencies: ['transformContent'], + extraDependencies: ['html', 'language'], + + relations: (relation, thing) => ({ + customName: + (thing.nameText + ? relation('transformContent', thing.nameText) + : null), + }), + + data: (thing) => ({ + normalName: + thing.name, + + shortName: + thing.nameShort, + }), + + slots: { + preferShortName: { + type: 'boolean', + default: false, + }, + }, + + generate: (data, relations, slots, {language}) => + (relations.customName + ? relations.customName.slot('mode', 'inline') + : slots.preferShortName && data.shortName + ? language.sanitize(data.shortName) + : language.sanitize(data.normalName)), +}; diff --git a/src/content/dependencies/generateNearbyTrackList.js b/src/content/dependencies/generateNearbyTrackList.js new file mode 100644 index 00000000..56ab2df5 --- /dev/null +++ b/src/content/dependencies/generateNearbyTrackList.js @@ -0,0 +1,44 @@ +export default { + query: (tracks, contextTrack, _contextContributions) => ({ + presentedTracks: + (contextTrack + ? tracks.map(track => + track.otherReleases.find(({album}) => album === contextTrack.album) ?? + track) + : tracks), + }), + + relations: (relation, query, _tracks, _contextTrack, contextContributions) => ({ + items: + query.presentedTracks + .map(track => relation('generateTrackListItem', track, contextContributions)), + }), + + slots: { + showArtists: { + validate: v => v.is(true, false, 'auto'), + default: 'auto', + }, + + showDuration: { + type: 'boolean', + default: false, + }, + + colorMode: { + validate: v => v.is('none', 'track', 'line'), + default: 'track', + }, + }, + + generate: (relations, slots, {html}) => + html.tag('ul', + {[html.onlyIfContent]: true}, + + relations.items.map(item => + item.slots({ + showArtists: slots.showArtists, + showDuration: slots.showDuration, + colorMode: slots.colorMode, + }))), +}; diff --git a/src/content/dependencies/generateNewsEntryNavAccent.js b/src/content/dependencies/generateNewsEntryNavAccent.js index 5d168e41..05248eb3 100644 --- a/src/content/dependencies/generateNewsEntryNavAccent.js +++ b/src/content/dependencies/generateNewsEntryNavAccent.js @@ -1,11 +1,4 @@ export default { - contentDependencies: [ - 'generateInterpageDotSwitcher', - 'generateNextLink', - 'generatePreviousLink', - 'linkNewsEntry', - ], - relations: (relation, previousEntry, nextEntry) => ({ switcher: relation('generateInterpageDotSwitcher'), diff --git a/src/content/dependencies/generateNewsEntryPage.js b/src/content/dependencies/generateNewsEntryPage.js index 4abd87d1..bbfb886d 100644 --- a/src/content/dependencies/generateNewsEntryPage.js +++ b/src/content/dependencies/generateNewsEntryPage.js @@ -2,16 +2,6 @@ import {sortChronologically} from '#sort'; import {atOffset} from '#sugar'; export default { - contentDependencies: [ - 'generateNewsEntryNavAccent', - 'generateNewsEntryReadAnotherLinks', - 'generatePageLayout', - 'linkNewsIndex', - 'transformContent', - ], - - extraDependencies: ['html', 'language', 'wikiData'], - sprawl({newsData}) { return {newsData}; }, diff --git a/src/content/dependencies/generateNewsEntryReadAnotherLinks.js b/src/content/dependencies/generateNewsEntryReadAnotherLinks.js index d978b0e4..1f6ee6d4 100644 --- a/src/content/dependencies/generateNewsEntryReadAnotherLinks.js +++ b/src/content/dependencies/generateNewsEntryReadAnotherLinks.js @@ -1,12 +1,4 @@ export default { - contentDependencies: [ - 'generateAbsoluteDatetimestamp', - 'generateRelativeDatetimestamp', - 'linkNewsEntry', - ], - - extraDependencies: ['html', 'language'], - relations(relation, currentEntry, previousEntry, nextEntry) { const relations = {}; @@ -57,10 +49,7 @@ export default { if (relations.previousEntryDatetimestamp) { parts.push('withDate'); options.date = - relations.previousEntryDatetimestamp.slots({ - style: 'full', - tooltip: true, - }); + relations.previousEntryDatetimestamp.slot('style', 'full'); } entryLines.push(language.$(...parts, options)); @@ -75,10 +64,7 @@ export default { if (relations.nextEntryDatetimestamp) { parts.push('withDate'); options.date = - relations.nextEntryDatetimestamp.slots({ - style: 'full', - tooltip: true, - }); + relations.nextEntryDatetimestamp.slot('style', 'full'); } entryLines.push(language.$(...parts, options)); diff --git a/src/content/dependencies/generateNewsIndexPage.js b/src/content/dependencies/generateNewsIndexPage.js index 02964ce8..d88bfdba 100644 --- a/src/content/dependencies/generateNewsIndexPage.js +++ b/src/content/dependencies/generateNewsIndexPage.js @@ -2,14 +2,6 @@ import {sortChronologically} from '#sort'; import {stitchArrays} from '#sugar'; export default { - contentDependencies: [ - 'generatePageLayout', - 'linkNewsEntry', - 'transformContent', - ], - - extraDependencies: ['html', 'language', 'wikiData'], - sprawl({newsData}) { return {newsData}; }, diff --git a/src/content/dependencies/generateNextLink.js b/src/content/dependencies/generateNextLink.js index 2e48cd2b..2c497e12 100644 --- a/src/content/dependencies/generateNextLink.js +++ b/src/content/dependencies/generateNextLink.js @@ -1,6 +1,4 @@ export default { - contentDependencies: ['generatePreviousNextLink'], - relations: (relation) => ({ link: relation('generatePreviousNextLink'), diff --git a/src/content/dependencies/generatePageLayout.js b/src/content/dependencies/generatePageLayout.js index 89fefb23..23d5932d 100644 --- a/src/content/dependencies/generatePageLayout.js +++ b/src/content/dependencies/generatePageLayout.js @@ -1,27 +1,9 @@ +import striptags from 'striptags'; + import {openAggregate} from '#aggregate'; import {atOffset, empty, repeat} from '#sugar'; export default { - contentDependencies: [ - 'generateColorStyleRules', - 'generateFooterLocalizationLinks', - 'generateImageOverlay', - 'generatePageSidebar', - 'generateSearchSidebarBox', - 'generateStickyHeadingContainer', - 'transformContent', - ], - - extraDependencies: [ - 'getColors', - 'html', - 'language', - 'pagePath', - 'pagePathStringFromRoot', - 'to', - 'wikiData', - ], - sprawl: ({wikiInfo}) => ({ enableSearch: wikiInfo.enableSearch, footerContent: wikiInfo.footerContent, @@ -58,8 +40,14 @@ export default { relation('transformContent', sprawl.footerContent); } - relations.colorStyleRules = - relation('generateColorStyleRules'); + relations.colorStyleTag = + relation('generateColorStyleTag'); + + relations.staticURLStyleTag = + relation('generateStaticURLStyleTag'); + + relations.wikiWallpaperStyleTag = + relation('generateWikiWallpaperStyleTag'); relations.imageOverlay = relation('generateImageOverlay'); @@ -107,9 +95,9 @@ export default { color: {validate: v => v.isColor}, - styleRules: { - validate: v => v.sparseArrayOf(v.isHTML), - default: [], + styleTags: { + type: 'html', + mutable: false, }, mainClasses: { @@ -288,12 +276,17 @@ export default { const titleContentsHTML = (html.isBlank(slots.title) ? null - : html.isBlank(slots.additionalNames) - ? language.sanitize(slots.title) - : html.tag('a', { + + : (!html.isBlank(slots.additionalNames) && + !html.resolve(slots.additionalNames, {slots: ['alwaysVisible']}) + .getSlotValue('alwaysVisible')) + + ? html.tag('a', { href: '#additional-names-box', title: language.$('misc.additionalNames.tooltip').toString(), - }, language.sanitize(slots.title))); + }, language.sanitize(slots.title)) + + : language.sanitize(slots.title)); const titleHTML = (html.isBlank(slots.title) @@ -581,29 +574,33 @@ export default { {id: 'additional-files', string: 'additionalFiles'}, {id: 'commentary', string: 'commentary'}, {id: 'artist-commentary', string: 'artistCommentary'}, - {id: 'credit-sources', string: 'creditSources'}, + {id: 'crediting-sources', string: 'creditingSources'}, + {id: 'referencing-sources', string: 'referencingSources'}, ])), ]); - const styleRulesCSS = - html.resolve(slots.styleRules, {normalize: 'string'}); + const slottedStyleTags = + html.smush(slots.styleTags); - const fallbackBackgroundStyleRule = - (styleRulesCSS.match(/body::before[^}]*background-image:/) - ? '' - : `body::before {\n` + - ` background-image: url("${to('media.path', 'bg.jpg')}");\n` + - `}`); + const slottedWallpaperStyleTag = + slottedStyleTags.content + .find(tag => tag.attributes.has('class', 'wallpaper-style')); - const goshFrigginDarnitStyleRule = - `.image-media-link::after {\n` + - ` mask-image: url("${to('staticMisc.path', 'image.svg')}");\n` + - `}`; + const fallbackWallpaperStyleTag = + (slottedWallpaperStyleTag + ? html.blank() + : relations.wikiWallpaperStyleTag); + + const usingWallpaperStyleTag = + (slottedWallpaperStyleTag + ? slottedWallpaperStyleTag + : html.resolve(fallbackWallpaperStyleTag, {normalize: 'tag'})); const numWallpaperParts = - html.resolve(slots.styleRules, {normalize: 'string'}) - .match(/\.wallpaper-part:nth-child/g) - ?.length ?? 0; + (usingWallpaperStyleTag && + usingWallpaperStyleTag.attributes.has('data-wallpaper-mode', 'parts') + ? parseInt(usingWallpaperStyleTag.attributes.get('data-num-wallpaper-parts')) + : 0); const wallpaperPartsHTML = html.tag('div', {class: 'wallpaper-parts'}, @@ -659,11 +656,25 @@ export default { language.encapsulate('misc.pageTitle', workingCapsule => { const workingOptions = {}; - workingOptions.title = slots.title; + // Slightly jank: The output of striptags is, of course, a string, + // and as far as language.formatString() is concerned, that means + // it needs to be sanitized - including turning ampersands into + // &'s. But the title is already HTML that has implicitly been + // sanitized, however it got here, and includes HTML entities that + // are properly escaped. Those need to get included as they are, + // so we wrap the title in a tag and pass it off as good to go. + workingOptions.title = + html.tags([ + striptags(slots.title.toString()), + ]); if (!html.isBlank(slots.subtitle)) { + // Same shenanigans here, as far as wrapping striptags goes. workingCapsule += '.withSubtitle'; - workingOptions.subtitle = slots.subtitle; + workingOptions.subtitle = + html.tags([ + striptags(slots.subtitle.toString()), + ]); } const showWikiName = @@ -745,14 +756,14 @@ export default { href: to('staticCSS.path', 'site.css'), }), - html.tag('style', [ - relations.colorStyleRules - .slot('color', slots.color ?? data.wikiColor), + relations.colorStyleTag + .slot('color', slots.color ?? data.wikiColor), - fallbackBackgroundStyleRule, - goshFrigginDarnitStyleRule, - slots.styleRules, - ]), + relations.staticURLStyleTag, + + fallbackWallpaperStyleTag, + + slottedStyleTags, html.tag('script', { src: to('staticLib.path', 'chroma-js/chroma.min.js'), diff --git a/src/content/dependencies/generatePageSidebar.js b/src/content/dependencies/generatePageSidebar.js index d3b55580..dfe85632 100644 --- a/src/content/dependencies/generatePageSidebar.js +++ b/src/content/dependencies/generatePageSidebar.js @@ -1,6 +1,4 @@ export default { - extraDependencies: ['html'], - slots: { // Attributes to apply to the whole sidebar. This be added to the // containing sidebar-column, arr - specify attributes on each section if diff --git a/src/content/dependencies/generatePageSidebarBox.js b/src/content/dependencies/generatePageSidebarBox.js index 26b30494..3133aa64 100644 --- a/src/content/dependencies/generatePageSidebarBox.js +++ b/src/content/dependencies/generatePageSidebarBox.js @@ -1,6 +1,4 @@ export default { - extraDependencies: ['html'], - slots: { content: { type: 'html', diff --git a/src/content/dependencies/generatePageSidebarConjoinedBox.js b/src/content/dependencies/generatePageSidebarConjoinedBox.js index 7974c707..4ed0ff22 100644 --- a/src/content/dependencies/generatePageSidebarConjoinedBox.js +++ b/src/content/dependencies/generatePageSidebarConjoinedBox.js @@ -4,9 +4,6 @@ // templates' resolved content), take care when slotting into this. export default { - contentDependencies: ['generatePageSidebarBox'], - extraDependencies: ['html'], - relations: (relation) => ({ box: relation('generatePageSidebarBox'), diff --git a/src/content/dependencies/generatePreviousLink.js b/src/content/dependencies/generatePreviousLink.js index 775367f9..29146a21 100644 --- a/src/content/dependencies/generatePreviousLink.js +++ b/src/content/dependencies/generatePreviousLink.js @@ -1,6 +1,4 @@ export default { - contentDependencies: ['generatePreviousNextLink'], - relations: (relation) => ({ link: relation('generatePreviousNextLink'), diff --git a/src/content/dependencies/generatePreviousNextLink.js b/src/content/dependencies/generatePreviousNextLink.js index afae1228..1e98358f 100644 --- a/src/content/dependencies/generatePreviousNextLink.js +++ b/src/content/dependencies/generatePreviousNextLink.js @@ -1,6 +1,4 @@ export default { - extraDependencies: ['html', 'language'], - slots: { link: { type: 'html', diff --git a/src/content/dependencies/generateQuickDescription.js b/src/content/dependencies/generateQuickDescription.js index e144503e..f67f9514 100644 --- a/src/content/dependencies/generateQuickDescription.js +++ b/src/content/dependencies/generateQuickDescription.js @@ -1,7 +1,4 @@ export default { - contentDependencies: ['transformContent'], - extraDependencies: ['html', 'language'], - query: (thing) => ({ hasDescription: !!thing.description, diff --git a/src/content/dependencies/generateReadCommentaryLine.js b/src/content/dependencies/generateReadCommentaryLine.js new file mode 100644 index 00000000..05700536 --- /dev/null +++ b/src/content/dependencies/generateReadCommentaryLine.js @@ -0,0 +1,43 @@ +import {empty} from '#sugar'; + +export default { + query: (thing) => ({ + entries: + (thing.isTrack + ? [...thing.commentary, ...thing.commentaryFromMainRelease] + : thing.commentary), + }), + + data: (query, _thing) => ({ + hasWikiEditorCommentary: + query.entries.some(entry => entry.isWikiEditorCommentary), + + onlyWikiEditorCommentary: + !empty(query.entries) && + query.entries.every(entry => entry.isWikiEditorCommentary), + + hasAnyCommentary: + !empty(query.entries), + }), + + generate: (data, {html, language}) => + language.encapsulate('releaseInfo.readCommentary', capsule => + language.$(capsule, { + [language.onlyIfOptions]: ['link'], + + link: + html.tag('a', + {[html.onlyIfContent]: true}, + + {href: '#artist-commentary'}, + + language.encapsulate(capsule, 'link', capsule => + (data.onlyWikiEditorCommentary + ? language.$(capsule, 'onlyWikiCommentary') + : data.hasWikiEditorCommentary + ? language.$(capsule, 'withWikiCommentary') + : data.hasAnyCommentary + ? language.$(capsule) + : html.blank()))), + })), +}; diff --git a/src/content/dependencies/generateReferencedArtworksPage.js b/src/content/dependencies/generateReferencedArtworksPage.js index 154b4762..2f47b7a5 100644 --- a/src/content/dependencies/generateReferencedArtworksPage.js +++ b/src/content/dependencies/generateReferencedArtworksPage.js @@ -1,14 +1,4 @@ export default { - contentDependencies: [ - 'generateCoverArtwork', - 'generateCoverGrid', - 'generatePageLayout', - 'image', - 'linkAnythingMan', - ], - - extraDependencies: ['html', 'language'], - relations: (relation, artwork) => ({ layout: relation('generatePageLayout'), @@ -47,7 +37,7 @@ export default { }), slots: { - styleRules: {type: 'html', mutable: false}, + styleTags: {type: 'html', mutable: false}, title: {type: 'html', mutable: false}, @@ -62,7 +52,7 @@ export default { subtitle: language.$(pageCapsule, 'subtitle'), color: data.color, - styleRules: slots.styleRules, + styleTags: slots.styleTags, artworkColumnContent: relations.cover.slots({ diff --git a/src/content/dependencies/generateReferencedTracksList.js b/src/content/dependencies/generateReferencedTracksList.js new file mode 100644 index 00000000..1d566ce9 --- /dev/null +++ b/src/content/dependencies/generateReferencedTracksList.js @@ -0,0 +1,29 @@ +export default { + relations: (relation, track) => ({ + previousProductionTrackList: + relation('generateNearbyTrackList', + track.previousProductionTracks, + track, + track.artistContribs), + + referencedTrackList: + relation('generateNearbyTrackList', + track.referencedTracks, + track, + []), + }), + + generate: (relations, {html, language}) => + html.tag('ul', {[html.onlyIfContent]: true}, [ + html.inside(relations.previousProductionTrackList) + .map(li => html.inside(li)) + .map(label => + html.tag('li', + language.$('trackList.item.previousProduction', + {track: label}))), + + html.inside(relations.referencedTrackList), + ]), +}; + + diff --git a/src/content/dependencies/generateReferencingArtworksPage.js b/src/content/dependencies/generateReferencingArtworksPage.js index 55977b37..abb92732 100644 --- a/src/content/dependencies/generateReferencingArtworksPage.js +++ b/src/content/dependencies/generateReferencingArtworksPage.js @@ -1,14 +1,4 @@ export default { - contentDependencies: [ - 'generateCoverArtwork', - 'generateCoverGrid', - 'generatePageLayout', - 'image', - 'linkAnythingMan', - ], - - extraDependencies: ['html', 'language'], - relations: (relation, artwork) => ({ layout: relation('generatePageLayout'), @@ -47,7 +37,7 @@ export default { }), slots: { - styleRules: {type: 'html', mutable: false}, + styleTags: {type: 'html', mutable: false}, title: {type: 'html', mutable: false}, @@ -62,7 +52,7 @@ export default { subtitle: language.$(pageCapsule, 'subtitle'), color: data.color, - styleRules: slots.styleRules, + styleTags: slots.styleTags, artworkColumnContent: relations.cover.slots({ diff --git a/src/content/dependencies/generateRelativeDatetimestamp.js b/src/content/dependencies/generateRelativeDatetimestamp.js index a997de0e..1415564e 100644 --- a/src/content/dependencies/generateRelativeDatetimestamp.js +++ b/src/content/dependencies/generateRelativeDatetimestamp.js @@ -1,12 +1,4 @@ export default { - contentDependencies: [ - 'generateAbsoluteDatetimestamp', - 'generateDatetimestampTemplate', - 'generateTooltip', - ], - - extraDependencies: ['html', 'language'], - data: (currentDate, referenceDate) => (currentDate.getTime() === referenceDate.getTime() ? {equal: true, date: currentDate} @@ -28,19 +20,11 @@ export default { validate: v => v.is('full', 'year'), default: 'full', }, - - tooltip: { - type: 'boolean', - default: false, - }, }, generate(data, relations, slots, {language}) { if (data.equal) { - return relations.fallback.slots({ - style: slots.style, - tooltip: slots.tooltip, - }); + return relations.fallback.slot('style', slots.style); } return relations.template.slots({ @@ -52,15 +36,14 @@ export default { : null), tooltip: - slots.tooltip && - relations.tooltip.slots({ - content: - language.formatRelativeDate(data.currentDate, data.referenceDate, { - considerRoundingDays: true, - approximate: true, - absolute: slots.style === 'year', - }), - }), + relations.tooltip.slots({ + content: + language.formatRelativeDate(data.currentDate, data.referenceDate, { + considerRoundingDays: true, + approximate: true, + absolute: slots.style === 'year', + }), + }), datetime: data.currentDate.toISOString(), diff --git a/src/content/dependencies/generateReleaseInfoContributionsLine.js b/src/content/dependencies/generateReleaseInfoContributionsLine.js index 016e0a2c..4353ccf4 100644 --- a/src/content/dependencies/generateReleaseInfoContributionsLine.js +++ b/src/content/dependencies/generateReleaseInfoContributionsLine.js @@ -1,16 +1,15 @@ export default { - contentDependencies: ['generateArtistCredit'], - extraDependencies: ['html'], - - relations: (relation, contributions) => ({ + relations: (relation, contributions, formatText) => ({ credit: - relation('generateArtistCredit', contributions, []), + relation('generateArtistCredit', contributions, [], formatText), }), slots: { stringKey: {type: 'string'}, featuringStringKey: {type: 'string'}, + additionalStringOptions: {validate: v => v.isObject}, + chronologyKind: {type: 'string'}, }, @@ -27,5 +26,6 @@ export default { normalStringKey: slots.stringKey, normalFeaturingStringKey: slots.featuringStringKey, + additionalStringOptions: slots.additionalStringOptions, }), }; diff --git a/src/content/dependencies/generateReleaseInfoListenLine.js b/src/content/dependencies/generateReleaseInfoListenLine.js new file mode 100644 index 00000000..97f248d6 --- /dev/null +++ b/src/content/dependencies/generateReleaseInfoListenLine.js @@ -0,0 +1,156 @@ +import {isExternalLinkContext} from '#external-links'; +import {empty, stitchArrays, unique} from '#sugar'; + +function getReleaseContext(urlString, { + _artistURLs, + albumArtistURLs, +}) { + const composerBandcampDomains = + albumArtistURLs + .filter(url => url.hostname.endsWith('.bandcamp.com')) + .map(url => url.hostname); + + const url = new URL(urlString); + + if (url.hostname === 'homestuck.bandcamp.com') { + return 'officialRelease'; + } + + if (composerBandcampDomains.includes(url.hostname)) { + return 'composerRelease'; + } + + return null; +} + +export default { + query(thing) { + const query = {}; + + query.album = + (thing.album + ? thing.album + : thing); + + query.urls = + (!empty(thing.urls) + ? thing.urls + : thing.album && + thing.album.style === 'single' && + thing.album.tracks[0] === thing + ? thing.album.urls + : []); + + query.artists = + thing.artistContribs + .map(contrib => contrib.artist); + + query.artistGroups = + query.artists + .flatMap(artist => artist.closelyLinkedGroups) + .map(({group}) => group); + + query.albumArtists = + query.album.artistContribs + .map(contrib => contrib.artist); + + query.albumArtistGroups = + query.albumArtists + .flatMap(artist => artist.closelyLinkedGroups) + .map(({group}) => group); + + return query; + }, + + relations: (relation, query, _thing) => ({ + links: + query.urls.map(url => relation('linkExternal', url)), + }), + + data(query, thing) { + const data = {}; + + data.name = thing.name; + + const artistURLs = + unique([ + ...query.artists.flatMap(artist => artist.urls), + ...query.artistGroups.flatMap(group => group.urls), + ]).map(url => new URL(url)); + + const albumArtistURLs = + unique([ + ...query.albumArtists.flatMap(artist => artist.urls), + ...query.albumArtistGroups.flatMap(group => group.urls), + ]).map(url => new URL(url)); + + const boundGetReleaseContext = urlString => + getReleaseContext(urlString, { + artistURLs, + albumArtistURLs, + }); + + let releaseContexts = + query.urls.map(boundGetReleaseContext); + + const albumReleaseContexts = + query.album.urls.map(boundGetReleaseContext); + + const presentReleaseContexts = + unique(releaseContexts.filter(Boolean)); + + const presentAlbumReleaseContexts = + unique(albumReleaseContexts.filter(Boolean)); + + if ( + presentReleaseContexts.length <= 1 && + presentAlbumReleaseContexts.length <= 1 + ) { + releaseContexts = + query.urls.map(() => null); + } + + data.releaseContexts = releaseContexts; + + return data; + }, + + slots: { + visibleWithoutLinks: { + type: 'boolean', + default: false, + }, + + context: { + validate: () => isExternalLinkContext, + default: 'generic', + }, + }, + + generate: (data, relations, slots, {html, language}) => + language.encapsulate('releaseInfo.listenOn', capsule => + (empty(relations.links) && slots.visibleWithoutLinks + ? language.$(capsule, 'noLinks', { + name: + html.tag('i', data.name), + }) + + : language.$('releaseInfo.listenOn', { + [language.onlyIfOptions]: ['links'], + + links: + language.formatDisjunctionList( + stitchArrays({ + link: relations.links, + releaseContext: data.releaseContexts, + }).map(({link, releaseContext}) => + link.slot('context', [ + ... + (Array.isArray(slots.context) + ? slots.context + : [slots.context]), + + releaseContext, + ]))), + }))), +}; diff --git a/src/content/dependencies/generateSearchSidebarBox.js b/src/content/dependencies/generateSearchSidebarBox.js index 308a1105..701a01ac 100644 --- a/src/content/dependencies/generateSearchSidebarBox.js +++ b/src/content/dependencies/generateSearchSidebarBox.js @@ -1,7 +1,4 @@ export default { - contentDependencies: ['generatePageSidebarBox'], - extraDependencies: ['html', 'language'], - relations: (relation) => ({ sidebarBox: relation('generatePageSidebarBox'), @@ -58,6 +55,23 @@ export default { language.$(capsule, 'artTag')), ]), + language.encapsulate(capsule, 'resultDisambiguator', capsule => [ + html.tag('template', {class: 'wiki-search-group-result-disambiguator-string'}, + language.$(capsule, 'group', { + disambiguator: html.tag('slot', {name: 'disambiguator'}), + })), + + html.tag('template', {class: 'wiki-search-flash-result-disambiguator-string'}, + language.$(capsule, 'flash', { + disambiguator: html.tag('slot', {name: 'disambiguator'}), + })), + + html.tag('template', {class: 'wiki-search-track-result-disambiguator-string'}, + language.$(capsule, 'track', { + disambiguator: html.tag('slot', {name: 'disambiguator'}), + })), + ]), + language.encapsulate(capsule, 'resultFilter', capsule => [ html.tag('template', {class: 'wiki-search-album-result-filter-string'}, language.$(capsule, 'album')), diff --git a/src/content/dependencies/generateSecondaryNav.js b/src/content/dependencies/generateSecondaryNav.js index 9ce7ce9b..63b3839b 100644 --- a/src/content/dependencies/generateSecondaryNav.js +++ b/src/content/dependencies/generateSecondaryNav.js @@ -1,6 +1,4 @@ export default { - extraDependencies: ['html'], - slots: { content: { type: 'html', diff --git a/src/content/dependencies/generateSecondaryNavParentSiblingsPart.js b/src/content/dependencies/generateSecondaryNavParentSiblingsPart.js index f204f1fb..fe7c17ac 100644 --- a/src/content/dependencies/generateSecondaryNavParentSiblingsPart.js +++ b/src/content/dependencies/generateSecondaryNavParentSiblingsPart.js @@ -1,15 +1,4 @@ export default { - contentDependencies: [ - 'generateColorStyleAttribute', - 'generateInterpageDotSwitcher', - 'generateNextLink', - 'generatePreviousLink', - 'linkAlbumDynamically', - 'linkGroup', - ], - - extraDependencies: ['html', 'language'], - relations: (relation) => ({ switcher: relation('generateInterpageDotSwitcher'), diff --git a/src/content/dependencies/generateSocialEmbed.js b/src/content/dependencies/generateSocialEmbed.js index 513ea518..5fa9376c 100644 --- a/src/content/dependencies/generateSocialEmbed.js +++ b/src/content/dependencies/generateSocialEmbed.js @@ -1,6 +1,4 @@ export default { - extraDependencies: ['absoluteTo', 'html', 'language', 'wikiData'], - sprawl({wikiInfo}) { return { canonicalBase: wikiInfo.canonicalBase, diff --git a/src/content/dependencies/generateStaticPage.js b/src/content/dependencies/generateStaticPage.js index 226152c7..485b802e 100644 --- a/src/content/dependencies/generateStaticPage.js +++ b/src/content/dependencies/generateStaticPage.js @@ -1,7 +1,4 @@ export default { - contentDependencies: ['generatePageLayout', 'transformContent'], - extraDependencies: ['html'], - relations(relation, staticPage) { return { layout: relation('generatePageLayout'), @@ -23,17 +20,19 @@ export default { title: data.name, headingMode: 'sticky', - styleRules: - (data.stylesheet - ? [data.stylesheet] - : []), + styleTags: [ + html.tag('style', {class: 'static-page-style'}, + {[html.onlyIfContent]: true}, + data.stylesheet), + ], mainClasses: ['long-content'], mainContent: [ relations.content, - data.script && - html.tag('script', data.script), + html.tag('script', + {[html.onlyIfContent]: true}, + data.script), ], navLinkStyle: 'hierarchical', diff --git a/src/content/dependencies/generateStaticURLStyleTag.js b/src/content/dependencies/generateStaticURLStyleTag.js new file mode 100644 index 00000000..443a4d08 --- /dev/null +++ b/src/content/dependencies/generateStaticURLStyleTag.js @@ -0,0 +1,20 @@ +export default { + relations: (relation) => ({ + styleTag: + relation('generateStyleTag'), + }), + + generate: (relations, {to}) => + relations.styleTag.slots({ + attributes: {class: 'static-url-style'}, + + rules: [ + { + select: '.image-media-link::after', + declare: [ + `mask-image: url("${to('staticMisc.path', 'image.svg')}");` + ], + }, + ], + }), +}; diff --git a/src/content/dependencies/generateStickyHeadingContainer.js b/src/content/dependencies/generateStickyHeadingContainer.js index ec3062a3..f7388d60 100644 --- a/src/content/dependencies/generateStickyHeadingContainer.js +++ b/src/content/dependencies/generateStickyHeadingContainer.js @@ -1,6 +1,4 @@ export default { - extraDependencies: ['html'], - slots: { rootAttributes: { type: 'attributes', diff --git a/src/content/dependencies/generateStyleTag.js b/src/content/dependencies/generateStyleTag.js new file mode 100644 index 00000000..cdeadcfe --- /dev/null +++ b/src/content/dependencies/generateStyleTag.js @@ -0,0 +1,46 @@ +import {empty} from '#sugar'; + +const indent = text => + text + .split('\n') + .map(line => ' '.repeat(4) + line) + .join('\n'); + +export default { + slots: { + attributes: { + type: 'attributes', + mutable: false, + }, + + rules: { + validate: v => + v.looseArrayOf( + v.validateProperties({ + select: v.isString, + declare: v.looseArrayOf(v.isString), + })), + }, + }, + + generate: (slots, {html}) => + html.tag('style', slots.attributes, + {[html.onlyIfContent]: true}, + + slots.rules + .filter(Boolean) + + .map(rule => ({ + select: rule.select, + declare: rule.declare.filter(Boolean), + })) + + .filter(rule => !empty(rule.declare)) + + .map(rule => + `${rule.select} {\n` + + indent(rule.declare.join('\n')) + '\n' + + `}`) + + .join('\n\n')), +}; diff --git a/src/content/dependencies/generateTextWithTooltip.js b/src/content/dependencies/generateTextWithTooltip.js index 49ce1f61..360cfebc 100644 --- a/src/content/dependencies/generateTextWithTooltip.js +++ b/src/content/dependencies/generateTextWithTooltip.js @@ -1,6 +1,4 @@ export default { - extraDependencies: ['html'], - slots: { attributes: { type: 'attributes', diff --git a/src/content/dependencies/generateTooltip.js b/src/content/dependencies/generateTooltip.js index b09ee230..6f23af6d 100644 --- a/src/content/dependencies/generateTooltip.js +++ b/src/content/dependencies/generateTooltip.js @@ -1,6 +1,4 @@ export default { - extraDependencies: ['html'], - slots: { attributes: { type: 'attributes', diff --git a/src/content/dependencies/generateTrackArtistCommentarySection.js b/src/content/dependencies/generateTrackArtistCommentarySection.js index e3041d3a..39a3e145 100644 --- a/src/content/dependencies/generateTrackArtistCommentarySection.js +++ b/src/content/dependencies/generateTrackArtistCommentarySection.js @@ -1,15 +1,6 @@ import {empty, stitchArrays} from '#sugar'; export default { - contentDependencies: [ - 'generateCommentaryEntry', - 'generateContentHeading', - 'linkAlbum', - 'linkTrack', - ], - - extraDependencies: ['html', 'language'], - query: (track) => ({ otherSecondaryReleasesWithCommentary: track.otherReleases @@ -18,8 +9,8 @@ export default { }), relations: (relation, query, track) => ({ - contentHeading: - relation('generateContentHeading'), + commentaryContentHeading: + relation('generateCommentaryContentHeading', track), mainReleaseTrackLink: (track.isSecondaryRelease @@ -28,7 +19,7 @@ export default { mainReleaseArtistCommentaryEntries: (track.isSecondaryRelease - ? track.mainReleaseTrack.commentary + ? track.commentaryFromMainRelease .map(entry => relation('generateCommentaryEntry', entry)) : null), @@ -78,54 +69,40 @@ export default { generate: (data, relations, {html, language}) => language.encapsulate('misc.artistCommentary', capsule => html.tags([ - relations.contentHeading.clone() - .slots({ - attributes: {id: 'artist-commentary'}, - title: language.$('misc.artistCommentary'), - }), + relations.commentaryContentHeading, + relations.artistCommentaryEntries, data.isSecondaryRelease && - html.tags([ - html.tag('p', {class: ['drop', 'commentary-drop']}, - {[html.onlyIfSiblings]: true}, - - language.encapsulate(capsule, 'info.fromMainRelease', workingCapsule => { - const workingOptions = {}; - - workingOptions.album = - relations.mainReleaseTrackLink.slots({ - content: - data.mainReleaseAlbumName, - - color: - data.mainReleaseAlbumColor, - }); - - if (data.name !== data.mainReleaseName) { - workingCapsule += '.namedDifferently'; - workingOptions.name = - html.tag('i', data.mainReleaseName); - } - - return language.$(workingCapsule, workingOptions); - })), - - relations.mainReleaseArtistCommentaryEntries, - ]), - - html.tags([ - data.isSecondaryRelease && - !html.isBlank(relations.mainReleaseArtistCommentaryEntries) && - html.tag('p', {class: ['drop', 'commentary-drop']}, - {[html.onlyIfSiblings]: true}, - - language.$(capsule, 'info.releaseSpecific', { - album: - relations.thisReleaseAlbumLink, - })), - - relations.artistCommentaryEntries, - ]), + html.tag('div', {class: 'inherited-commentary-section'}, + {[html.onlyIfContent]: true}, + + [ + html.tag('p', {class: ['drop', 'commentary-drop']}, + {[html.onlyIfSiblings]: true}, + + language.encapsulate(capsule, 'info.fromMainRelease', workingCapsule => { + const workingOptions = {}; + + workingOptions.album = + relations.mainReleaseTrackLink.slots({ + content: + data.mainReleaseAlbumName, + + color: + data.mainReleaseAlbumColor, + }); + + if (data.name !== data.mainReleaseName) { + workingCapsule += '.namedDifferently'; + workingOptions.name = + html.tag('i', data.mainReleaseName); + } + + return language.$(workingCapsule, workingOptions); + })), + + relations.mainReleaseArtistCommentaryEntries, + ]), html.tag('p', {class: ['drop', 'commentary-drop']}, {[html.onlyIfContent]: true}, diff --git a/src/content/dependencies/generateTrackArtworkColumn.js b/src/content/dependencies/generateTrackArtworkColumn.js index f06d735b..234586e0 100644 --- a/src/content/dependencies/generateTrackArtworkColumn.js +++ b/src/content/dependencies/generateTrackArtworkColumn.js @@ -1,7 +1,4 @@ export default { - contentDependencies: ['generateCoverArtwork'], - extraDependencies: ['html'], - relations: (relation, track) => ({ albumCover: (!track.hasUniqueCoverArt && track.album.hasCoverArt diff --git a/src/content/dependencies/generateTrackInfoPage.js b/src/content/dependencies/generateTrackInfoPage.js index 11d179ad..d3c2d766 100644 --- a/src/content/dependencies/generateTrackInfoPage.js +++ b/src/content/dependencies/generateTrackInfoPage.js @@ -1,45 +1,46 @@ -export default { - contentDependencies: [ - 'generateAdditionalNamesBox', - 'generateAlbumAdditionalFilesList', - 'generateAlbumNavAccent', - 'generateAlbumSecondaryNav', - 'generateAlbumSidebar', - 'generateAlbumStyleRules', - 'generateCommentaryEntry', - 'generateContentHeading', - 'generateContributionList', - 'generateLyricsSection', - 'generatePageLayout', - 'generateTrackArtistCommentarySection', - 'generateTrackArtworkColumn', - 'generateTrackInfoPageFeaturedByFlashesList', - 'generateTrackInfoPageOtherReleasesList', - 'generateTrackList', - 'generateTrackListDividedByGroups', - 'generateTrackNavLinks', - 'generateTrackReleaseInfo', - 'generateTrackSocialEmbed', - 'linkAlbum', - 'linkTrack', - 'transformContent', - ], - - extraDependencies: ['html', 'language'], +function checkInterrupted(which, relations, {html}) { + if ( + !html.isBlank(relations.additionalFilesList) || + !html.isBlank(relations.contributorContributionList) || + !html.isBlank(relations.flashesThatFeatureList) || + !html.isBlank(relations.lyricsSection) || + !html.isBlank(relations.midiProjectFilesList) || + !html.isBlank(relations.referencedByTracksList) || + !html.isBlank(relations.referencedTracksList) || + !html.isBlank(relations.sampledByTracksList) || + !html.isBlank(relations.sampledTracksList) || + !html.isBlank(relations.sheetMusicFilesList) + ) return true; + + if (which === 'crediting-sources' || which === 'referencing-sources') { + if (!html.isBlank(relations.artistCommentarySection)) return true; + } + + return false; +} +export default { query: (track) => ({ mainReleaseTrack: (track.isMainRelease ? track : track.mainReleaseTrack), + + singleTrackSingle: + track.album.style === 'single' && + track.album.tracks.length === 1, + + firstTrackInSingle: + track.album.style === 'single' && + track === track.album.tracks[0], }), relations: (relation, query, track) => ({ layout: relation('generatePageLayout'), - albumStyleRules: - relation('generateAlbumStyleRules', track.album, track), + albumStyleTags: + relation('generateAlbumStyleTags', track.album, track), socialEmbed: relation('generateTrackSocialEmbed', track), @@ -47,6 +48,9 @@ export default { navLinks: relation('generateTrackNavLinks', track), + albumNavLink: + relation('linkAlbum', track.album), + albumNavAccent: relation('generateAlbumNavAccent', track.album, track), @@ -60,33 +64,46 @@ export default { relation('generateAdditionalNamesBox', track.additionalNames), artworkColumn: - relation('generateTrackArtworkColumn', track), + (query.firstTrackInSingle + ? relation('generateAlbumArtworkColumn', track.album) + : relation('generateTrackArtworkColumn', track)), contentHeading: relation('generateContentHeading'), + name: + relation('generateName', track), + releaseInfo: relation('generateTrackReleaseInfo', track), - otherReleasesList: - relation('generateTrackInfoPageOtherReleasesList', track), + readCommentaryLine: + relation('generateReadCommentaryLine', track), + + otherReleasesLine: + relation('generateTrackInfoPageOtherReleasesLine', track), + + previousProductionLine: + relation('generateTrackInfoPagePreviousProductionLine', track), contributorContributionList: relation('generateContributionList', track.contributorContribs), referencedTracksList: - relation('generateTrackList', track.referencedTracks), + relation('generateReferencedTracksList', track), sampledTracksList: - relation('generateTrackList', track.sampledTracks), + relation('generateNearbyTrackList', track.sampledTracks, track, []), referencedByTracksList: relation('generateTrackListDividedByGroups', - query.mainReleaseTrack.referencedByTracks), + query.mainReleaseTrack.referencedByTracks, + track), sampledByTracksList: relation('generateTrackListDividedByGroups', - query.mainReleaseTrack.sampledByTracks), + query.mainReleaseTrack.sampledByTracks, + track), flashesThatFeatureList: relation('generateTrackInfoPageFeaturedByFlashesList', track), @@ -95,34 +112,46 @@ export default { relation('generateLyricsSection', track.lyrics), sheetMusicFilesList: - relation('generateAlbumAdditionalFilesList', - track.album, - track.sheetMusicFiles), + relation('generateAdditionalFilesList', track.sheetMusicFiles), midiProjectFilesList: - relation('generateAlbumAdditionalFilesList', - track.album, - track.midiProjectFiles), + relation('generateAdditionalFilesList', track.midiProjectFiles), additionalFilesList: - relation('generateAlbumAdditionalFilesList', - track.album, - track.additionalFiles), + relation('generateAdditionalFilesList', track.additionalFiles), artistCommentarySection: relation('generateTrackArtistCommentarySection', track), - creditSourceEntries: - track.creditSources - .map(entry => relation('generateCommentaryEntry', entry)), + creditingSourcesSection: + relation('generateCollapsedContentEntrySection', + track.creditingSources, + track), + + referencingSourcesSection: + relation('generateCollapsedContentEntrySection', + track.referencingSources, + track), }), - data: (_query, track) => ({ + data: (query, track) => ({ name: track.name, color: track.color, + + dateAlbumAddedToWiki: + track.album.dateAddedToWiki, + + needsLyrics: + track.needsLyrics, + + singleTrackSingle: + query.singleTrackSingle, + + firstTrackInSingle: + query.firstTrackInSingle, }), generate: (data, relations, {html, language}) => @@ -130,7 +159,7 @@ export default { relations.layout.slots({ title: language.$(pageCapsule, 'title', { - track: data.name, + track: relations.name, }), headingMode: 'sticky', @@ -138,7 +167,7 @@ export default { additionalNames: relations.additionalNamesBox, color: data.color, - styleRules: [relations.albumStyleRules], + styleTags: relations.albumStyleTags, artworkColumnContent: relations.artworkColumn, @@ -178,26 +207,35 @@ export default { language.$(capsule, 'link')), })), - !html.isBlank(relations.artistCommentarySection) && - language.encapsulate(capsule, 'readCommentary', capsule => + checkInterrupted('commentary', relations, {html}) && + relations.readCommentaryLine, + + !html.isBlank(relations.creditingSourcesSection) && + checkInterrupted('crediting-sources', relations, {html}) && + language.encapsulate(capsule, 'readCreditingSources', capsule => language.$(capsule, { link: html.tag('a', - {href: '#artist-commentary'}, + {href: '#crediting-sources'}, language.$(capsule, 'link')), })), - !html.isBlank(relations.creditSourceEntries) && - language.encapsulate(capsule, 'readCreditSources', capsule => + !html.isBlank(relations.referencingSourcesSection) && + checkInterrupted('referencing-sources', relations, {html}) && + language.encapsulate(capsule, 'readReferencingSources', capsule => language.$(capsule, { link: html.tag('a', - {href: '#credit-sources'}, + {href: '#referencing-sources'}, language.$(capsule, 'link')), })), ])), - relations.otherReleasesList, + html.tag('p', {[html.onlyIfContent]: true}, + relations.otherReleasesLine), + + html.tag('p', {[html.onlyIfContent]: true}, + relations.previousProductionLine), html.tags([ relations.contentHeading.clone() @@ -309,6 +347,25 @@ export default { relations.flashesThatFeatureList, ]), + data.firstTrackInSingle && + html.tag('p', + {[html.onlyIfContent]: true}, + + language.$('releaseInfo.addedToWiki', { + [language.onlyIfOptions]: ['date'], + date: language.formatDate(data.dateAlbumAddedToWiki), + })), + + data.firstTrackInSingle && + (!html.isBlank(relations.lyricsSection) || + !html.isBlank(relations.artistCommentarySection)) && + html.tag('hr', {class: 'main-separator'}), + + data.needsLyrics && + html.isBlank(relations.lyricsSection) && + html.tag('p', + language.$(pageCapsule, 'needsLyrics')), + relations.lyricsSection, html.tags([ @@ -343,29 +400,40 @@ export default { relations.artistCommentarySection, - html.tags([ - relations.contentHeading.clone() - .slots({ - attributes: {id: 'credit-sources'}, - title: language.$('misc.creditSources'), - }), + relations.creditingSourcesSection.slots({ + id: 'crediting-sources', + string: 'misc.creditingSources', + }), - relations.creditSourceEntries, - ]), + relations.referencingSourcesSection.slots({ + id: 'referencing-sources', + string: 'misc.referencingSources', + }), ], navLinkStyle: 'hierarchical', - navLinks: html.resolve(relations.navLinks), + navLinks: + (data.singleTrackSingle + ? [ + {auto: 'home'}, + { + html: relations.albumNavLink, + accent: language.$(pageCapsule, 'nav.singleAccent'), + }, + ] + : html.resolve(relations.navLinks)), navBottomRowContent: - relations.albumNavAccent.slots({ - showTrackNavigation: true, - showExtraLinks: false, - }), + (data.singleTrackSingle + ? null + : relations.albumNavAccent.slots({ + showTrackNavigation: true, + showExtraLinks: false, + })), secondaryNav: relations.secondaryNav - .slot('mode', 'track'), + .slot('mode', data.firstTrackInSingle ? 'album' : 'track'), leftSidebar: relations.sidebar, diff --git a/src/content/dependencies/generateTrackInfoPageFeaturedByFlashesList.js b/src/content/dependencies/generateTrackInfoPageFeaturedByFlashesList.js index 61654512..cd7bb014 100644 --- a/src/content/dependencies/generateTrackInfoPageFeaturedByFlashesList.js +++ b/src/content/dependencies/generateTrackInfoPageFeaturedByFlashesList.js @@ -2,9 +2,6 @@ import {sortFlashesChronologically} from '#sort'; import {stitchArrays} from '#sugar'; export default { - contentDependencies: ['linkFlash', 'linkTrack'], - extraDependencies: ['html', 'language', 'wikiData'], - sprawl: ({wikiInfo}) => ({ enableFlashesAndGames: wikiInfo.enableFlashesAndGames, diff --git a/src/content/dependencies/generateTrackInfoPageOtherReleasesLine.js b/src/content/dependencies/generateTrackInfoPageOtherReleasesLine.js new file mode 100644 index 00000000..1793b73f --- /dev/null +++ b/src/content/dependencies/generateTrackInfoPageOtherReleasesLine.js @@ -0,0 +1,80 @@ +import {onlyItem, stitchArrays} from '#sugar'; + +export default { + query(track) { + const query = {}; + + query.singleSingle = + onlyItem( + track.otherReleases.filter(track => track.album.style === 'single')); + + query.regularReleases = + (query.singleSingle + ? track.otherReleases.filter(track => track !== query.singleSingle) + : track.otherReleases); + + return query; + }, + + relations: (relation, query, _track) => ({ + singleLink: + (query.singleSingle + ? relation('linkTrack', query.singleSingle) + : null), + + trackLinks: + query.regularReleases + .map(track => relation('linkTrack', track)), + }), + + data: (query, _track) => ({ + albumNames: + query.regularReleases + .map(track => track.album.name), + + albumColors: + query.regularReleases + .map(track => track.album.color), + }), + + generate: (data, relations, {html, language}) => + language.encapsulate('releaseInfo.alsoReleased', capsule => + language.encapsulate(capsule, workingCapsule => { + const workingOptions = {}; + + let any = false; + + const albumList = + language.formatConjunctionList( + stitchArrays({ + trackLink: relations.trackLinks, + albumName: data.albumNames, + albumColor: data.albumColors, + }).map(({trackLink, albumName, albumColor}) => + trackLink.slots({ + content: language.sanitize(albumName), + color: albumColor, + }))); + + if (!html.isBlank(albumList)) { + any = true; + workingCapsule += '.onAlbums'; + workingOptions.albums = albumList; + } + + if (relations.singleLink) { + any = true; + workingCapsule += '.asSingle'; + workingOptions.single = + relations.singleLink.slots({ + content: language.$(capsule, 'single'), + }); + } + + if (any) { + return language.$(workingCapsule, workingOptions); + } else { + return html.blank(); + } + })), +}; diff --git a/src/content/dependencies/generateTrackInfoPageOtherReleasesList.js b/src/content/dependencies/generateTrackInfoPageOtherReleasesList.js deleted file mode 100644 index ebd76577..00000000 --- a/src/content/dependencies/generateTrackInfoPageOtherReleasesList.js +++ /dev/null @@ -1,42 +0,0 @@ -import {stitchArrays} from '#sugar'; - -export default { - contentDependencies: ['linkTrack'], - extraDependencies: ['html', 'language'], - - relations: (relation, track) => ({ - trackLinks: - track.otherReleases - .map(track => relation('linkTrack', track)), - }), - - data: (track) => ({ - albumNames: - track.otherReleases - .map(track => track.album.name), - - albumColors: - track.otherReleases - .map(track => track.album.color), - }), - - generate: (data, relations, {html, language}) => - html.tag('p', - {[html.onlyIfContent]: true}, - - language.$('releaseInfo.alsoReleasedOn', { - [language.onlyIfOptions]: ['albums'], - - albums: - language.formatConjunctionList( - stitchArrays({ - trackLink: relations.trackLinks, - albumName: data.albumNames, - albumColor: data.albumColors, - }).map(({trackLink, albumName, albumColor}) => - trackLink.slots({ - content: language.sanitize(albumName), - color: albumColor, - }))), - })), -}; diff --git a/src/content/dependencies/generateTrackInfoPagePreviousProductionLine.js b/src/content/dependencies/generateTrackInfoPagePreviousProductionLine.js new file mode 100644 index 00000000..b2f50cf3 --- /dev/null +++ b/src/content/dependencies/generateTrackInfoPagePreviousProductionLine.js @@ -0,0 +1,37 @@ +import {stitchArrays} from '#sugar'; +import {getKebabCase} from '#wiki-data'; + +export default { + relations: (relation, track) => ({ + trackLinks: + track.followingProductionTracks + .map(track => relation('linkTrack', track)), + + albumLinks: + track.followingProductionTracks + .map(following => + (following.album !== track.album && + getKebabCase(following.name) === getKebabCase(track.name) + + ? relation('linkAlbum', following.album) + : null)), + }), + + generate: (relations, {language}) => + language.encapsulate('releaseInfo.previousProduction', capsule => + language.$(capsule, { + [language.onlyIfOptions]: ['tracks'], + + tracks: + stitchArrays({ + trackLink: relations.trackLinks, + albumLink: relations.albumLinks, + }).map(({trackLink, albumLink}) => + (albumLink + ? language.$(capsule, 'trackOnAlbum', { + track: trackLink, + album: albumLink, + }) + : trackLink)), + })), +}; diff --git a/src/content/dependencies/generateTrackList.js b/src/content/dependencies/generateTrackList.js index 53a32536..c259c914 100644 --- a/src/content/dependencies/generateTrackList.js +++ b/src/content/dependencies/generateTrackList.js @@ -1,14 +1,21 @@ export default { - contentDependencies: ['generateTrackListItem'], - extraDependencies: ['html'], - - relations: (relation, tracks) => ({ + relations: (relation, tracks, contextContributions) => ({ items: - tracks - .map(track => relation('generateTrackListItem', track, [])), + tracks.map(track => + relation('generateTrackListItem', track, contextContributions)), }), slots: { + showArtists: { + validate: v => v.is(true, false, 'auto'), + default: 'auto', + }, + + showDuration: { + type: 'boolean', + default: false, + }, + colorMode: { validate: v => v.is('none', 'track', 'line'), default: 'track', @@ -21,8 +28,8 @@ export default { relations.items.map(item => item.slots({ - showArtists: true, - showDuration: false, + showArtists: slots.showArtists, + showDuration: slots.showDuration, colorMode: slots.colorMode, }))), }; diff --git a/src/content/dependencies/generateTrackListDividedByGroups.js b/src/content/dependencies/generateTrackListDividedByGroups.js index 230868d6..419d7c0f 100644 --- a/src/content/dependencies/generateTrackListDividedByGroups.js +++ b/src/content/dependencies/generateTrackListDividedByGroups.js @@ -1,20 +1,12 @@ import {empty, filterMultipleArrays, stitchArrays} from '#sugar'; export default { - contentDependencies: [ - 'generateContentHeading', - 'generateTrackList', - 'linkGroup', - ], - - extraDependencies: ['html', 'language', 'wikiData'], - sprawl: ({wikiInfo}) => ({ divideTrackListsByGroups: wikiInfo.divideTrackListsByGroups, }), - query(sprawl, tracks) { + query(sprawl, tracks, _contextTrack) { const dividingGroups = sprawl.divideTrackListsByGroups; const groupings = new Map(); @@ -50,10 +42,10 @@ export default { return {groups, groupedTracks, ungroupedTracks}; }, - relations: (relation, query, sprawl, tracks) => ({ + relations: (relation, query, sprawl, tracks, contextTrack) => ({ flatList: (empty(sprawl.divideTrackListsByGroups) - ? relation('generateTrackList', tracks) + ? relation('generateNearbyTrackList', tracks, contextTrack, []) : null), contentHeading: @@ -65,12 +57,12 @@ export default { groupedTrackLists: query.groupedTracks - .map(tracks => relation('generateTrackList', tracks)), + .map(tracks => relation('generateNearbyTrackList', tracks, contextTrack, [])), ungroupedTrackList: (empty(query.ungroupedTracks) ? null - : relation('generateTrackList', query.ungroupedTracks)), + : relation('generateNearbyTrackList', query.ungroupedTracks, contextTrack, [])), }), data: (query, _sprawl, _tracks) => ({ diff --git a/src/content/dependencies/generateTrackListItem.js b/src/content/dependencies/generateTrackListItem.js index 3c850a18..c4b7c21e 100644 --- a/src/content/dependencies/generateTrackListItem.js +++ b/src/content/dependencies/generateTrackListItem.js @@ -1,21 +1,19 @@ export default { - contentDependencies: [ - 'generateArtistCredit', - 'generateColorStyleAttribute', - 'generateTrackListMissingDuration', - 'linkTrack', - ], - - extraDependencies: ['html', 'language'], - relations: (relation, track, contextContributions) => ({ trackLink: relation('linkTrack', track), - credit: + contextualCredit: + relation('generateArtistCredit', + track.artistContribs, + contextContributions, + track.artistText), + + acontextualCredit: relation('generateArtistCredit', track.artistContribs, - contextContributions), + [], + track.artistText), colorStyle: relation('generateColorStyleAttribute', track.color), @@ -35,12 +33,11 @@ export default { }), slots: { - // showArtists enables showing artists *at all.* It doesn't take precedence - // over behavior which automatically collapses (certain) artists because of - // provided context contributions. + // true always shows artists, false never does; 'auto' shows only if + // the track's artists differ from the given context contributions. showArtists: { - type: 'boolean', - default: true, + validate: v => v.is(true, false, 'auto'), + default: 'auto', }, // If true and the track doesn't have a duration, a missing-duration cue @@ -80,26 +77,33 @@ export default { : relations.missingDuration); } - const artistCapsule = language.encapsulate(itemCapsule, 'withArtists'); - - relations.credit.setSlots({ - normalStringKey: - artistCapsule + '.by', - - featuringStringKey: - artistCapsule + '.featuring', - - normalFeaturingStringKey: - artistCapsule + '.by.featuring', - }); - - if (!html.isBlank(relations.credit)) { - workingCapsule += '.withArtists'; - workingOptions.by = - html.tag('span', {class: 'by'}, - // TODO: This is obviously evil. - html.metatag('chunkwrap', {split: /,| (?=and)/}, - html.resolve(relations.credit))); + const chosenCredit = + (slots.showArtists === true + ? relations.acontextualCredit + : slots.showArtists === 'auto' + ? relations.contextualCredit + : null); + + if (chosenCredit) { + const artistCapsule = language.encapsulate(itemCapsule, 'withArtists'); + + chosenCredit.setSlots({ + normalStringKey: + artistCapsule + '.by', + + featuringStringKey: + artistCapsule + '.featuring', + + normalFeaturingStringKey: + artistCapsule + '.by.featuring', + }); + + if (!html.isBlank(chosenCredit)) { + workingCapsule += '.withArtists'; + workingOptions.by = + html.tag('span', {class: 'by'}, + chosenCredit); + } } return language.$(workingCapsule, workingOptions); diff --git a/src/content/dependencies/generateTrackListMissingDuration.js b/src/content/dependencies/generateTrackListMissingDuration.js index b5917982..da3113a2 100644 --- a/src/content/dependencies/generateTrackListMissingDuration.js +++ b/src/content/dependencies/generateTrackListMissingDuration.js @@ -1,7 +1,4 @@ export default { - contentDependencies: ['generateTextWithTooltip', 'generateTooltip'], - extraDependencies: ['html', 'language'], - relations: (relation) => ({ textWithTooltip: relation('generateTextWithTooltip'), diff --git a/src/content/dependencies/generateTrackNavLinks.js b/src/content/dependencies/generateTrackNavLinks.js index 6a8b7c64..d18e6cad 100644 --- a/src/content/dependencies/generateTrackNavLinks.js +++ b/src/content/dependencies/generateTrackNavLinks.js @@ -1,7 +1,4 @@ export default { - contentDependencies: ['linkAlbum', 'linkTrack'], - extraDependencies: ['html', 'language'], - relations: (relation, track) => ({ albumLink: relation('linkAlbum', track.album), @@ -11,6 +8,9 @@ export default { }), data: (track) => ({ + albumStyle: + track.album.style, + hasTrackNumbers: track.album.hasTrackNumbers, @@ -28,7 +28,13 @@ export default { language.encapsulate('trackPage.nav', navCapsule => [ {auto: 'home'}, - {html: relations.albumLink.slot('color', false)}, + { + html: relations.albumLink.slot('color', false), + accent: + (data.albumStyle === 'single' + ? language.$(navCapsule, 'singleAccent') + : null), + }, { html: diff --git a/src/content/dependencies/generateTrackReferencedArtworksPage.js b/src/content/dependencies/generateTrackReferencedArtworksPage.js index 93438c5b..a2612067 100644 --- a/src/content/dependencies/generateTrackReferencedArtworksPage.js +++ b/src/content/dependencies/generateTrackReferencedArtworksPage.js @@ -1,19 +1,10 @@ export default { - contentDependencies: [ - 'generateAlbumStyleRules', - 'generateBackToTrackLink', - 'generateReferencedArtworksPage', - 'generateTrackNavLinks', - ], - - extraDependencies: ['html', 'language'], - relations: (relation, track) => ({ page: relation('generateReferencedArtworksPage', track.trackArtworks[0]), - albumStyleRules: - relation('generateAlbumStyleRules', track.album, track), + albumStyleTags: + relation('generateAlbumStyleTags', track.album, track), navLinks: relation('generateTrackNavLinks', track), @@ -35,7 +26,7 @@ export default { data.name, }), - styleRules: [relations.albumStyleRules], + styleTags: relations.albumStyleTags, navLinks: html.resolve( diff --git a/src/content/dependencies/generateTrackReferencingArtworksPage.js b/src/content/dependencies/generateTrackReferencingArtworksPage.js index e9818bad..be13dd79 100644 --- a/src/content/dependencies/generateTrackReferencingArtworksPage.js +++ b/src/content/dependencies/generateTrackReferencingArtworksPage.js @@ -1,19 +1,10 @@ export default { - contentDependencies: [ - 'generateAlbumStyleRules', - 'generateBackToTrackLink', - 'generateReferencingArtworksPage', - 'generateTrackNavLinks', - ], - - extraDependencies: ['html', 'language'], - relations: (relation, track) => ({ page: relation('generateReferencingArtworksPage', track.trackArtworks[0]), - albumStyleRules: - relation('generateAlbumStyleRules', track.album, track), + albumStyleTags: + relation('generateAlbumStyleTags', track.album, track), navLinks: relation('generateTrackNavLinks', track), @@ -35,7 +26,7 @@ export default { data.name, }), - styleRules: [relations.albumStyleRules], + styleTags: relations.albumStyleTags, navLinks: html.resolve( diff --git a/src/content/dependencies/generateTrackReleaseBox.js b/src/content/dependencies/generateTrackReleaseBox.js index ef02e2b9..c880fe63 100644 --- a/src/content/dependencies/generateTrackReleaseBox.js +++ b/src/content/dependencies/generateTrackReleaseBox.js @@ -1,12 +1,4 @@ export default { - contentDependencies: [ - 'generateColorStyleAttribute', - 'generatePageSidebarBox', - 'linkTrack', - ], - - extraDependencies: ['html', 'language'], - relations: (relation, track) => ({ box: relation('generatePageSidebarBox'), diff --git a/src/content/dependencies/generateTrackReleaseInfo.js b/src/content/dependencies/generateTrackReleaseInfo.js index 54e462c7..0207e574 100644 --- a/src/content/dependencies/generateTrackReleaseInfo.js +++ b/src/content/dependencies/generateTrackReleaseInfo.js @@ -1,24 +1,19 @@ -import {empty} from '#sugar'; +import {compareArrays} from '#sugar'; export default { - contentDependencies: [ - 'generateReleaseInfoContributionsLine', - 'linkExternal', - ], - - extraDependencies: ['html', 'language'], - relations(relation, track) { const relations = {}; - relations.artistContributionLinks = - relation('generateReleaseInfoContributionsLine', track.artistContribs); + relations.artistContributionsLine = + relation('generateReleaseInfoContributionsLine', + track.artistContribs, + track.artistText); - if (!empty(track.urls)) { - relations.externalLinks = - track.urls.map(url => - relation('linkExternal', url)); - } + relations.listenLine = + relation('generateReleaseInfoListenLine', track); + + relations.albumLink = + relation('linkAlbum', track.album); return relations; }, @@ -30,6 +25,16 @@ export default { data.date = track.date; data.duration = track.duration; + const {album} = track; + + data.showAlbum = + album.showAlbumInTracksWithoutArtists && + track.artistContribs.every(({annotation}) => !annotation) && + compareArrays( + track.artistContribs.map(({artist}) => artist), + album.artistContribs.map(({artist}) => artist), + {checkOrder: true}); + if ( track.hasUniqueCoverArt && +track.coverArtDate !== +track.date @@ -48,10 +53,21 @@ export default { {[html.joinChildren]: html.tag('br')}, [ - relations.artistContributionLinks.slots({ - stringKey: capsule + '.by', - featuringStringKey: capsule + '.by.featuring', - chronologyKind: 'track', + language.encapsulate(capsule, 'by', capsule => { + const withAlbum = + (data.showAlbum ? '.withAlbum' : ''); + + const albumOptions = + (data.showAlbum ? {album: relations.albumLink} : {}); + + return relations.artistContributionsLine.slots({ + stringKey: capsule + withAlbum, + featuringStringKey: capsule + '.featuring' + withAlbum, + + additionalStringOptions: albumOptions, + + chronologyKind: 'track', + }); }), language.$(capsule, 'released', { @@ -66,17 +82,9 @@ export default { ]), html.tag('p', - language.encapsulate(capsule, 'listenOn', capsule => - (relations.externalLinks - ? language.$(capsule, { - links: - language.formatDisjunctionList( - relations.externalLinks - .map(link => link.slot('context', 'track'))), - }) - : language.$(capsule, 'noLinks', { - name: - html.tag('i', data.name), - })))), + relations.listenLine.slots({ + visibleWithoutLinks: true, + context: ['track'], + })), ])), }; diff --git a/src/content/dependencies/generateTrackSocialEmbed.js b/src/content/dependencies/generateTrackSocialEmbed.js index 7cb37af2..94453f7d 100644 --- a/src/content/dependencies/generateTrackSocialEmbed.js +++ b/src/content/dependencies/generateTrackSocialEmbed.js @@ -1,11 +1,4 @@ export default { - contentDependencies: [ - 'generateSocialEmbed', - 'generateTrackSocialEmbedDescription', - ], - - extraDependencies: ['absoluteTo', 'language'], - relations(relation, track) { return { socialEmbed: @@ -26,14 +19,12 @@ export default { data.trackDirectory = track.directory; data.albumDirectory = album.directory; + data.hasImage = track.hasUniqueCoverArt || album.hasCoverArt; + if (track.hasUniqueCoverArt) { - data.imageSource = 'track'; - data.coverArtFileExtension = track.coverArtFileExtension; + data.imagePath = track.trackArtworks[0].path; } else if (album.hasCoverArt) { - data.imageSource = 'album'; - data.coverArtFileExtension = album.coverArtFileExtension; - } else { - data.imageSource = 'none'; + data.imagePath = album.coverArtworks[0].path; } return data; @@ -59,10 +50,8 @@ export default { absoluteTo('localized.album', data.albumDirectory), imagePath: - (data.imageSource === 'album' - ? ['media.albumCover', data.albumDirectory, data.coverArtFileExtension] - : data.imageSource === 'track' - ? ['media.trackCover', data.albumDirectory, data.trackDirectory, data.coverArtFileExtension] + (data.hasImage + ? data.imagePath : null), })), }; diff --git a/src/content/dependencies/generateTrackSocialEmbedDescription.js b/src/content/dependencies/generateTrackSocialEmbedDescription.js index 4706aa26..97a4017f 100644 --- a/src/content/dependencies/generateTrackSocialEmbedDescription.js +++ b/src/content/dependencies/generateTrackSocialEmbedDescription.js @@ -1,8 +1,6 @@ import {empty} from '#sugar'; export default { - extraDependencies: ['html', 'language'], - data: (track) => ({ artistNames: track.artistContribs diff --git a/src/content/dependencies/generateUnsafeMunchy.js b/src/content/dependencies/generateUnsafeMunchy.js index c11aadc7..df8231ef 100644 --- a/src/content/dependencies/generateUnsafeMunchy.js +++ b/src/content/dependencies/generateUnsafeMunchy.js @@ -1,6 +1,4 @@ export default { - extraDependencies: ['html'], - slots: { contentSource: {type: 'string'}, }, diff --git a/src/content/dependencies/generateWallpaperStyleTag.js b/src/content/dependencies/generateWallpaperStyleTag.js new file mode 100644 index 00000000..b89f01c2 --- /dev/null +++ b/src/content/dependencies/generateWallpaperStyleTag.js @@ -0,0 +1,77 @@ +import {empty, stitchArrays} from '#sugar'; + +export default { + relations: (relation) => ({ + styleTag: + relation('generateStyleTag'), + }), + + slots: { + singleWallpaperPath: { + validate: v => v.strictArrayOf(v.isString), + }, + + singleWallpaperStyle: { + validate: v => v.isString, + }, + + wallpaperPartPaths: { + validate: v => + v.strictArrayOf(v.optional(v.strictArrayOf(v.isString))), + }, + + wallpaperPartStyles: { + validate: v => + v.strictArrayOf(v.optional(v.isString)), + }, + }, + + generate(relations, slots, {html, to}) { + const attributes = html.attributes(); + const rules = []; + + attributes.add('class', 'wallpaper-style'); + + if (empty(slots.wallpaperPartPaths)) { + attributes.set('data-wallpaper-mode', 'one'); + + rules.push({ + select: 'body::before', + declare: [ + `background-image: url("${to(...slots.singleWallpaperPath)}");`, + slots.singleWallpaperStyle, + ], + }); + } else { + attributes.set('data-wallpaper-mode', 'parts'); + attributes.set('data-num-wallpaper-parts', slots.wallpaperPartPaths.length); + + stitchArrays({ + path: slots.wallpaperPartPaths, + style: slots.wallpaperPartStyles, + }).forEach(({path, style}, index) => { + rules.push({ + select: `.wallpaper-part:nth-child(${index + 1})`, + declare: [ + path && `background-image: url("${to(...path)}");`, + style, + ], + }); + }); + + rules.push({ + select: 'body::before', + declare: [ + 'display: none;', + ], + }); + } + + relations.styleTag.setSlots({ + attributes, + rules, + }); + + return relations.styleTag; + }, +}; diff --git a/src/content/dependencies/generateWikiHomepageActionsRow.js b/src/content/dependencies/generateWikiHomepageActionsRow.js index 9f501099..5e3ff381 100644 --- a/src/content/dependencies/generateWikiHomepageActionsRow.js +++ b/src/content/dependencies/generateWikiHomepageActionsRow.js @@ -1,6 +1,4 @@ export default { - contentDependencies: ['generateGridActionLinks', 'transformContent'], - relations: (relation, row) => ({ template: relation('generateGridActionLinks'), diff --git a/src/content/dependencies/generateWikiHomepageAlbumCarouselRow.js b/src/content/dependencies/generateWikiHomepageAlbumCarouselRow.js index b45bfc19..8f4b3400 100644 --- a/src/content/dependencies/generateWikiHomepageAlbumCarouselRow.js +++ b/src/content/dependencies/generateWikiHomepageAlbumCarouselRow.js @@ -1,6 +1,4 @@ export default { - contentDependencies: ['generateCoverCarousel', 'image', 'linkAlbum'], - relations: (relation, row) => ({ coverCarousel: relation('generateCoverCarousel'), diff --git a/src/content/dependencies/generateWikiHomepageAlbumGridRow.js b/src/content/dependencies/generateWikiHomepageAlbumGridRow.js index a00136ba..eb3417d7 100644 --- a/src/content/dependencies/generateWikiHomepageAlbumGridRow.js +++ b/src/content/dependencies/generateWikiHomepageAlbumGridRow.js @@ -2,9 +2,6 @@ import {empty, stitchArrays} from '#sugar'; import {getNewAdditions, getNewReleases} from '#wiki-data'; export default { - contentDependencies: ['generateCoverGrid', 'image', 'linkAlbum'], - extraDependencies: ['language', 'wikiData'], - sprawl({albumData}, row) { const sprawl = {}; @@ -21,8 +18,7 @@ export default { sprawl.albums = (row.sourceGroup ? row.sourceGroup.albums - .slice() - .reverse() + .toReversed() .filter(album => album.isListedOnHomepage) .slice(0, row.countAlbumsFromGroup) : []); diff --git a/src/content/dependencies/generateWikiHomepageNewsBox.js b/src/content/dependencies/generateWikiHomepageNewsBox.js index 83a27695..3a06a7c3 100644 --- a/src/content/dependencies/generateWikiHomepageNewsBox.js +++ b/src/content/dependencies/generateWikiHomepageNewsBox.js @@ -1,14 +1,6 @@ import {stitchArrays} from '#sugar'; export default { - contentDependencies: [ - 'generatePageSidebarBox', - 'linkNewsEntry', - 'transformContent', - ], - - extraDependencies: ['html', 'language', 'wikiData'], - sprawl: ({newsData}) => ({ entries: newsData.slice(0, 3), diff --git a/src/content/dependencies/generateWikiHomepagePage.js b/src/content/dependencies/generateWikiHomepagePage.js index 8c09a007..9029131b 100644 --- a/src/content/dependencies/generateWikiHomepagePage.js +++ b/src/content/dependencies/generateWikiHomepagePage.js @@ -1,15 +1,4 @@ export default { - contentDependencies: [ - 'generatePageLayout', - 'generatePageSidebar', - 'generatePageSidebarBox', - 'generateWikiHomepageNewsBox', - 'generateWikiHomepageSection', - 'transformContent', - ], - - extraDependencies: ['wikiData'], - sprawl: ({wikiInfo}) => ({ wikiName: wikiInfo.name, diff --git a/src/content/dependencies/generateWikiHomepageSection.js b/src/content/dependencies/generateWikiHomepageSection.js index 49a474da..5fc0c76f 100644 --- a/src/content/dependencies/generateWikiHomepageSection.js +++ b/src/content/dependencies/generateWikiHomepageSection.js @@ -1,13 +1,4 @@ export default { - contentDependencies: [ - 'generateColorStyleAttribute', - 'generateWikiHomepageActionsRow', - 'generateWikiHomepageAlbumCarouselRow', - 'generateWikiHomepageAlbumGridRow', - ], - - extraDependencies: ['html'], - relations: (relation, homepageSection) => ({ colorStyle: relation('generateColorStyleAttribute', homepageSection.color), diff --git a/src/content/dependencies/generateWikiWallpaperStyleTag.js b/src/content/dependencies/generateWikiWallpaperStyleTag.js new file mode 100644 index 00000000..be52bcc1 --- /dev/null +++ b/src/content/dependencies/generateWikiWallpaperStyleTag.js @@ -0,0 +1,35 @@ +export default { + sprawl: ({wikiInfo}) => ({wikiInfo}), + + relations: (relation) => ({ + wallpaperStyleTag: + relation('generateWallpaperStyleTag'), + }), + + data: ({wikiInfo}) => ({ + singleWallpaperPath: [ + 'media.path', + 'bg.' + wikiInfo.wikiWallpaperFileExtension, + ], + + singleWallpaperStyle: + wikiInfo.wikiWallpaperStyle, + + wallpaperPartPaths: + wikiInfo.wikiWallpaperParts.map(part => + (part.asset + ? ['media.path', part.asset] + : null)), + + wallpaperPartStyles: + wikiInfo.wikiWallpaperParts.map(part => part.style), + }), + + generate: (data, relations) => + relations.wallpaperStyleTag.slots({ + singleWallpaperPath: data.singleWallpaperPath, + singleWallpaperStyle: data.singleWallpaperStyle, + wallpaperPartPaths: data.wallpaperPartPaths, + wallpaperPartStyles: data.wallpaperPartStyles, + }), +}; diff --git a/src/content/dependencies/image.js b/src/content/dependencies/image.js index bf47b14f..d979b0bc 100644 --- a/src/content/dependencies/image.js +++ b/src/content/dependencies/image.js @@ -2,20 +2,6 @@ import {logWarn} from '#cli'; import {empty} from '#sugar'; export default { - extraDependencies: [ - 'checkIfImagePathHasCachedThumbnails', - 'getDimensionsOfImagePath', - 'getSizeOfMediaFile', - 'getThumbnailEqualOrSmaller', - 'getThumbnailsAvailableForDimensions', - 'html', - 'language', - 'missingImagePaths', - 'to', - ], - - contentDependencies: ['generateColorStyleAttribute'], - relations: (relation, _artwork) => ({ colorStyle: relation('generateColorStyleAttribute'), @@ -42,6 +28,8 @@ export default { slots: { thumb: {type: 'string'}, + responsiveThumb: {type: 'boolean', default: false}, + responsiveSizes: {type: 'string'}, reveal: {type: 'boolean', default: true}, lazy: {type: 'boolean', default: false}, @@ -60,6 +48,12 @@ export default { mutable: false, }, + // Added to the <img>. + imgAttributes: { + type: 'attributes', + mutable: false, + }, + // Added to the <img> itself. alt: {type: 'string'}, @@ -141,6 +135,8 @@ export default { const imgAttributes = html.attributes([ {class: 'image'}, + slots.imgAttributes, + slots.alt && {alt: slots.alt}, dimensions && @@ -205,31 +201,29 @@ export default { // so it won't be set if thumbnails aren't available. let revealSrc = null; + let originalDimensions; + let availableThumbs; + let selectedThumbtack; + + const getThumbSrc = (thumbtack) => + to('thumb.path', mediaSrc.replace(/\.(png|jpg)$/, `.${thumbtack}.jpg`)); + // If thumbnails are available *and* being used, calculate thumbSrc, // and provide some attributes relevant to the large image overlay. if (hasThumbnails && slots.thumb) { - const selectedSize = + selectedThumbtack = getThumbnailEqualOrSmaller(slots.thumb, mediaSrc); - const mediaSrcJpeg = - mediaSrc.replace(/\.(png|jpg)$/, `.${selectedSize}.jpg`); - displaySrc = - to('thumb.path', mediaSrcJpeg); + getThumbSrc(selectedThumbtack); if (willReveal) { - const miniSize = - getThumbnailEqualOrSmaller('mini', mediaSrc); - - const mediaSrcJpeg = - mediaSrc.replace(/\.(png|jpg)$/, `.${miniSize}.jpg`); - revealSrc = - to('thumb.path', mediaSrcJpeg); + getThumbSrc(getThumbnailEqualOrSmaller('mini', mediaSrc)); } - const originalDimensions = getDimensionsOfImagePath(mediaSrc); - const availableThumbs = getThumbnailsAvailableForDimensions(originalDimensions); + originalDimensions = getDimensionsOfImagePath(mediaSrc); + availableThumbs = getThumbnailsAvailableForDimensions(originalDimensions); const fileSize = (willLink && mediaSrc @@ -245,11 +239,54 @@ export default { !empty(availableThumbs) && {'data-thumbs': availableThumbs - .map(([name, size]) => `${name}:${size}`) + .map(([tack, size]) => `${tack}:${size}`) .join(' ')}, ]); } + let displayStaticImg = + html.tag('img', + imgAttributes, + {src: displaySrc}); + + if (hasThumbnails && slots.responsiveThumb) responsive: { + if (slots.lazy) { + logWarn`${'responsiveThumb'} and ${'lazy'} are used together, but not compatible`; + break responsive; + } + + if (!slots.thumb) { + logWarn`${'responsiveThumb'} must be used alongside a default ${'thumb'}`; + break responsive; + } + + const srcset = [ + // Never load the original source, which might be a very large + // uncompressed file. Bah! + /* [originalSrc, `${Math.min(...originalDimensions)}w`], */ + + ...availableThumbs.map(([tack, size]) => + [getThumbSrc(tack), `${Math.floor(0.95 * size)}w`]), + + // fallback + [displaySrc], + ].map(line => line.join(' ')).join(',\n'); + + displayStaticImg = + html.tag('img', + imgAttributes, + + {sizes: + (slots.responsiveSizes.match(/(?=(?:,|^))\s*\S/) + // slot provided fallback size + ? slots.responsiveSizes + // default fallback size + : slots.responsiveSizes + ',\n' + + new Map(availableThumbs).get(selectedThumbtack) + 'px')}, + + {srcset}); + } + if (!displaySrc) { return ( prepare( @@ -258,10 +295,7 @@ export default { } const images = { - displayStatic: - html.tag('img', - imgAttributes, - {src: displaySrc}), + displayStatic: displayStaticImg, displayLazy: slots.lazy && diff --git a/src/content/dependencies/index.js b/src/content/dependencies/index.js index a5009804..cfa6346c 100644 --- a/src/content/dependencies/index.js +++ b/src/content/dependencies/index.js @@ -11,6 +11,11 @@ import {colors, logWarn} from '#cli'; import contentFunction, {ContentFunctionSpecError} from '#content-function'; import {annotateFunction} from '#sugar'; +const __dirname = path.dirname(fileURLToPath(import.meta.url)); + +const codeSrcPath = path.resolve(__dirname, '..'); +const codeRootPath = path.resolve(codeSrcPath, '..'); + function cachebust(filePath) { if (filePath in cachebust.cache) { cachebust.cache[filePath] += 1; @@ -42,7 +47,9 @@ export function watchContentDependencies({ close, }); - const eslint = new ESLint(); + const eslint = new ESLint({ + cwd: codeRootPath, + }); const metaPath = fileURLToPath(import.meta.url); const metaDirname = path.dirname(metaPath); @@ -87,6 +94,8 @@ export function watchContentDependencies({ const filePaths = files.map(file => path.join(watchPath, file)); for (const filePath of filePaths) { if (filePath === metaPath) continue; + if (filePath.endsWith('.DS_Store')) continue; + const functionName = getFunctionName(filePath); if (!isMocked(functionName)) { contentDependencies[functionName] = null; @@ -98,8 +107,9 @@ export function watchContentDependencies({ watcher.on('all', (event, filePath) => { if (!['add', 'change'].includes(event)) return; if (filePath === metaPath) return; - handlePathUpdated(filePath); + if (filePath.endsWith('.DS_Store')) return; + handlePathUpdated(filePath); }); watcher.on('unlink', (filePath) => { @@ -108,6 +118,8 @@ export function watchContentDependencies({ return; } + if (filePath.endsWith('.DS_Store')) return; + handlePathRemoved(filePath); }); diff --git a/src/content/dependencies/linkAdditionalFile.js b/src/content/dependencies/linkAdditionalFile.js new file mode 100644 index 00000000..1b5e650f --- /dev/null +++ b/src/content/dependencies/linkAdditionalFile.js @@ -0,0 +1,27 @@ +export default { + query: (file, filename) => ({ + index: + file.filenames.indexOf(filename), + }), + + relations: (relation, _query, _file, _filename) => ({ + linkTemplate: + relation('linkTemplate'), + }), + + data: (query, file, filename) => ({ + filename, + + // Kinda jank, but eh. + path: + (query.index >= 0 + ? file.paths.at(query.index) + : null), + }), + + generate: (data, relations) => + relations.linkTemplate.slots({ + path: data.path, + content: data.filename, + }), +}; diff --git a/src/content/dependencies/linkAlbum.js b/src/content/dependencies/linkAlbum.js index 36b0d13a..085d5f62 100644 --- a/src/content/dependencies/linkAlbum.js +++ b/src/content/dependencies/linkAlbum.js @@ -1,8 +1,18 @@ export default { - contentDependencies: ['linkThing'], + relations: (relation, album) => ({ + link: + (album.style === 'single' + ? relation('linkTrack', album.tracks[0]) + : relation('linkThing', 'localized.album', album)), + }), - relations: (relation, album) => - ({link: relation('linkThing', 'localized.album', album)}), + data: (album) => ({ + style: album.style, + name: album.name, + }), - generate: (relations) => relations.link, + generate: (data, relations, {language}) => + (data.style === 'single' + ? relations.link.slot('content', language.sanitize(data.name)) + : relations.link), }; diff --git a/src/content/dependencies/linkAlbumAdditionalFile.js b/src/content/dependencies/linkAlbumAdditionalFile.js deleted file mode 100644 index 39e7111e..00000000 --- a/src/content/dependencies/linkAlbumAdditionalFile.js +++ /dev/null @@ -1,24 +0,0 @@ -export default { - contentDependencies: ['linkTemplate'], - - relations(relation) { - return { - linkTemplate: relation('linkTemplate'), - }; - }, - - data(album, file) { - return { - albumDirectory: album.directory, - file, - }; - }, - - generate(data, relations) { - return relations.linkTemplate - .slots({ - path: ['media.albumAdditionalFile', data.albumDirectory, data.file], - content: data.file, - }); - }, -}; diff --git a/src/content/dependencies/linkAlbumCommentary.js b/src/content/dependencies/linkAlbumCommentary.js index ab519fd6..f1917345 100644 --- a/src/content/dependencies/linkAlbumCommentary.js +++ b/src/content/dependencies/linkAlbumCommentary.js @@ -1,6 +1,4 @@ export default { - contentDependencies: ['linkThing'], - relations: (relation, album) => ({link: relation('linkThing', 'localized.albumCommentary', album)}), diff --git a/src/content/dependencies/linkAlbumDynamically.js b/src/content/dependencies/linkAlbumDynamically.js index 45f8c2a9..ba572c8d 100644 --- a/src/content/dependencies/linkAlbumDynamically.js +++ b/src/content/dependencies/linkAlbumDynamically.js @@ -1,14 +1,6 @@ import {empty} from '#sugar'; export default { - contentDependencies: [ - 'linkAlbumCommentary', - 'linkAlbumGallery', - 'linkAlbum', - ], - - extraDependencies: ['html', 'pagePath'], - relations: (relation, album) => ({ galleryLink: relation('linkAlbumGallery', album), diff --git a/src/content/dependencies/linkAlbumGallery.js b/src/content/dependencies/linkAlbumGallery.js index e3f30a29..efba66d1 100644 --- a/src/content/dependencies/linkAlbumGallery.js +++ b/src/content/dependencies/linkAlbumGallery.js @@ -1,6 +1,4 @@ export default { - contentDependencies: ['linkThing'], - relations: (relation, album) => ({link: relation('linkThing', 'localized.albumGallery', album)}), diff --git a/src/content/dependencies/linkAlbumReferencedArtworks.js b/src/content/dependencies/linkAlbumReferencedArtworks.js index ba51b5e3..411bd2ab 100644 --- a/src/content/dependencies/linkAlbumReferencedArtworks.js +++ b/src/content/dependencies/linkAlbumReferencedArtworks.js @@ -1,6 +1,4 @@ export default { - contentDependencies: ['linkThing'], - relations: (relation, album) => ({link: relation('linkThing', 'localized.albumReferencedArtworks', album)}), diff --git a/src/content/dependencies/linkAlbumReferencingArtworks.js b/src/content/dependencies/linkAlbumReferencingArtworks.js index 4d5e799d..3aee9a4b 100644 --- a/src/content/dependencies/linkAlbumReferencingArtworks.js +++ b/src/content/dependencies/linkAlbumReferencingArtworks.js @@ -1,6 +1,4 @@ export default { - contentDependencies: ['linkThing'], - relations: (relation, album) => ({link: relation('linkThing', 'localized.albumReferencingArtworks', album)}), diff --git a/src/content/dependencies/linkAnythingMan.js b/src/content/dependencies/linkAnythingMan.js index e408c1b2..cb22baee 100644 --- a/src/content/dependencies/linkAnythingMan.js +++ b/src/content/dependencies/linkAnythingMan.js @@ -1,24 +1,13 @@ export default { - contentDependencies: [ - 'linkAlbum', - 'linkArtwork', - 'linkFlash', - 'linkTrack', - ], - - query: (thing) => ({ - referenceType: thing.constructor[Symbol.for('Thing.referenceType')], - }), - - relations: (relation, query, thing) => ({ + relations: (relation, thing) => ({ link: - (query.referenceType === 'album' + (thing.isAlbum ? relation('linkAlbum', thing) - : query.referenceType === 'artwork' + : thing.isArtwork ? relation('linkArtwork', thing) - : query.referenceType === 'flash' + : thing.isFlash ? relation('linkFlash', thing) - : query.referenceType === 'track' + : thing.isTrack ? relation('linkTrack', thing) : null), }), diff --git a/src/content/dependencies/linkArtTagDynamically.js b/src/content/dependencies/linkArtTagDynamically.js index 964258e1..4514b7c1 100644 --- a/src/content/dependencies/linkArtTagDynamically.js +++ b/src/content/dependencies/linkArtTagDynamically.js @@ -1,7 +1,4 @@ export default { - contentDependencies: ['linkArtTagGallery', 'linkArtTagInfo'], - extraDependencies: ['pagePath'], - relations: (relation, artTag) => ({ galleryLink: relation('linkArtTagGallery', artTag), infoLink: relation('linkArtTagInfo', artTag), diff --git a/src/content/dependencies/linkArtTagGallery.js b/src/content/dependencies/linkArtTagGallery.js index a92b69c1..92ab1ed3 100644 --- a/src/content/dependencies/linkArtTagGallery.js +++ b/src/content/dependencies/linkArtTagGallery.js @@ -1,6 +1,4 @@ export default { - contentDependencies: ['linkThing'], - relations: (relation, artTag) => ({link: relation('linkThing', 'localized.artTagGallery', artTag)}), diff --git a/src/content/dependencies/linkArtTagInfo.js b/src/content/dependencies/linkArtTagInfo.js index 409cb3c0..5eb2ac56 100644 --- a/src/content/dependencies/linkArtTagInfo.js +++ b/src/content/dependencies/linkArtTagInfo.js @@ -1,6 +1,4 @@ export default { - contentDependencies: ['linkThing'], - relations: (relation, artTag) => ({link: relation('linkThing', 'localized.artTagInfo', artTag)}), diff --git a/src/content/dependencies/linkArtist.js b/src/content/dependencies/linkArtist.js index 718ee6fa..917ae6b6 100644 --- a/src/content/dependencies/linkArtist.js +++ b/src/content/dependencies/linkArtist.js @@ -1,6 +1,4 @@ export default { - contentDependencies: ['linkThing'], - relations: (relation, artist) => ({link: relation('linkThing', 'localized.artist', artist)}), diff --git a/src/content/dependencies/linkArtistGallery.js b/src/content/dependencies/linkArtistGallery.js index 66dc172d..001eec1f 100644 --- a/src/content/dependencies/linkArtistGallery.js +++ b/src/content/dependencies/linkArtistGallery.js @@ -1,6 +1,4 @@ export default { - contentDependencies: ['linkThing'], - relations: (relation, artist) => ({link: relation('linkThing', 'localized.artistGallery', artist)}), diff --git a/src/content/dependencies/linkArtistRollingWindow.js b/src/content/dependencies/linkArtistRollingWindow.js new file mode 100644 index 00000000..6ab516ac --- /dev/null +++ b/src/content/dependencies/linkArtistRollingWindow.js @@ -0,0 +1,6 @@ +export default { + relations: (relation, artist) => + ({link: relation('linkThing', 'localized.artistRollingWindow', artist)}), + + generate: (relations) => relations.link, +}; diff --git a/src/content/dependencies/linkArtwork.js b/src/content/dependencies/linkArtwork.js index 8cd6f359..fce89229 100644 --- a/src/content/dependencies/linkArtwork.js +++ b/src/content/dependencies/linkArtwork.js @@ -1,16 +1,9 @@ export default { - contentDependencies: ['linkAlbum', 'linkTrack'], - - query: (artwork) => ({ - referenceType: - artwork.thing.constructor[Symbol.for('Thing.referenceType')], - }), - - relations: (relation, query, artwork) => ({ + relations: (relation, artwork) => ({ link: - (query.referenceType === 'album' + (artwork.thing.isAlbum ? relation('linkAlbum', artwork.thing) - : query.referenceType === 'track' + : artwork.thing.isTrack ? relation('linkTrack', artwork.thing) : null), }), diff --git a/src/content/dependencies/linkCommentaryIndex.js b/src/content/dependencies/linkCommentaryIndex.js index 5568ff84..e59b3641 100644 --- a/src/content/dependencies/linkCommentaryIndex.js +++ b/src/content/dependencies/linkCommentaryIndex.js @@ -1,6 +1,4 @@ export default { - contentDependencies: ['linkStationaryIndex'], - relations: (relation) => ({link: relation( diff --git a/src/content/dependencies/linkContribution.js b/src/content/dependencies/linkContribution.js index c658d461..aa9bdef9 100644 --- a/src/content/dependencies/linkContribution.js +++ b/src/content/dependencies/linkContribution.js @@ -1,12 +1,4 @@ export default { - contentDependencies: [ - 'generateContributionTooltip', - 'generateTextWithTooltip', - 'linkArtist', - ], - - extraDependencies: ['html', 'language'], - relations: (relation, contribution) => ({ artistLink: relation('linkArtist', contribution.artist), @@ -24,13 +16,15 @@ export default { }), slots: { + content: {type: 'html', mutable: false}, + showAnnotation: {type: 'boolean', default: false}, showExternalLinks: {type: 'boolean', default: false}, showChronology: {type: 'boolean', default: false}, trimAnnotation: {type: 'boolean', default: false}, - preventWrapping: {type: 'boolean', default: true}, + preventWrapping: {type: 'boolean', default: false}, preventTooltip: {type: 'boolean', default: false}, chronologyKind: {type: 'string'}, @@ -46,6 +40,10 @@ export default { language.encapsulate('misc.artistLink', workingCapsule => { const workingOptions = {}; + if (!html.isBlank(slots.content)) { + relations.artistLink.setSlot('content', slots.content); + } + // Filling slots early is necessary to actually give the tooltip // content. Otherwise, the coming-up html.isBlank() always reports // the tooltip as blank! diff --git a/src/content/dependencies/linkExternal.js b/src/content/dependencies/linkExternal.js index c132baaf..ad8d4f23 100644 --- a/src/content/dependencies/linkExternal.js +++ b/src/content/dependencies/linkExternal.js @@ -1,9 +1,20 @@ import {isExternalLinkContext, isExternalLinkStyle} from '#external-links'; export default { - extraDependencies: ['html', 'language', 'wikiData'], + sprawl: ({wikiInfo}) => ({ + canonicalBase: + wikiInfo.canonicalBase, - data: (url) => ({url}), + canonicalMediaBase: + wikiInfo.canonicalMediaBase, + }), + + data: (sprawl, url) => ({ + url, + + canonicalBase: + sprawl.canonicalBase, + }), slots: { content: { @@ -50,19 +61,33 @@ export default { }, }, - generate(data, slots, {html, language}) { + generate(data, slots, {html, language, to}) { + const {url} = data; + let urlIsValid; try { - new URL(data.url); + new URL(url); urlIsValid = true; - } catch (error) { + } catch { urlIsValid = false; } + let href; + if (urlIsValid) { + const {canonicalBase, canonicalMediaBase} = data; + if (canonicalMediaBase && url.startsWith(canonicalMediaBase)) { + href = to('media.path', url.slice(canonicalMediaBase.length)); + } else if (canonicalBase && url.startsWith(canonicalBase)) { + href = to('shared.path', url.slice(canonicalBase.length)); + } else { + href = url; + } + } + let formattedLink; if (urlIsValid) { formattedLink = - language.formatExternalLink(data.url, { + language.formatExternalLink(url, { style: slots.style, context: slots.context, }); @@ -70,7 +95,7 @@ export default { // Fall back to platform if nothing matched the desired style. if (html.isBlank(formattedLink) && slots.style !== 'platform') { formattedLink = - language.formatExternalLink(data.url, { + language.formatExternalLink(url, { style: 'platform', context: slots.context, }); @@ -85,7 +110,7 @@ export default { let linkContent; if (urlIsValid) { - linkAttributes.set('href', data.url); + linkAttributes.set('href', href); if (html.isBlank(slots.content)) { linkContent = formattedLink; diff --git a/src/content/dependencies/linkFlash.js b/src/content/dependencies/linkFlash.js index 93dd5a28..cfc01079 100644 --- a/src/content/dependencies/linkFlash.js +++ b/src/content/dependencies/linkFlash.js @@ -1,6 +1,4 @@ export default { - contentDependencies: ['linkThing'], - relations: (relation, flash) => ({link: relation('linkThing', 'localized.flash', flash)}), diff --git a/src/content/dependencies/linkFlashAct.js b/src/content/dependencies/linkFlashAct.js index 82c23325..069bedf4 100644 --- a/src/content/dependencies/linkFlashAct.js +++ b/src/content/dependencies/linkFlashAct.js @@ -1,6 +1,4 @@ export default { - contentDependencies: ['generateUnsafeMunchy', 'linkThing'], - relations: (relation, flashAct) => ({ unsafeMunchy: relation('generateUnsafeMunchy'), diff --git a/src/content/dependencies/linkFlashIndex.js b/src/content/dependencies/linkFlashIndex.js index 6dd0710e..9c1b076e 100644 --- a/src/content/dependencies/linkFlashIndex.js +++ b/src/content/dependencies/linkFlashIndex.js @@ -1,6 +1,4 @@ export default { - contentDependencies: ['linkStationaryIndex'], - relations: (relation) => ({link: relation( diff --git a/src/content/dependencies/linkFlashSide.js b/src/content/dependencies/linkFlashSide.js index b77ca65a..6407ef25 100644 --- a/src/content/dependencies/linkFlashSide.js +++ b/src/content/dependencies/linkFlashSide.js @@ -1,6 +1,4 @@ export default { - contentDependencies: ['linkFlashAct'], - relations: (relation, flashSide) => ({ link: relation('linkFlashAct', flashSide.acts[0]), diff --git a/src/content/dependencies/linkGroup.js b/src/content/dependencies/linkGroup.js index ebab1b5b..10bec2fb 100644 --- a/src/content/dependencies/linkGroup.js +++ b/src/content/dependencies/linkGroup.js @@ -1,6 +1,4 @@ export default { - contentDependencies: ['linkThing'], - relations: (relation, group) => ({link: relation('linkThing', 'localized.groupInfo', group)}), diff --git a/src/content/dependencies/linkGroupDynamically.js b/src/content/dependencies/linkGroupDynamically.js index 90303ed1..0b5bd85c 100644 --- a/src/content/dependencies/linkGroupDynamically.js +++ b/src/content/dependencies/linkGroupDynamically.js @@ -1,7 +1,4 @@ export default { - contentDependencies: ['linkGroupGallery', 'linkGroup'], - extraDependencies: ['pagePath'], - relations: (relation, group) => ({ galleryLink: relation('linkGroupGallery', group), infoLink: relation('linkGroup', group), diff --git a/src/content/dependencies/linkGroupExtra.js b/src/content/dependencies/linkGroupExtra.js index bc3c0580..1a6161c1 100644 --- a/src/content/dependencies/linkGroupExtra.js +++ b/src/content/dependencies/linkGroupExtra.js @@ -1,13 +1,6 @@ import {empty} from '#sugar'; export default { - contentDependencies: [ - 'linkGroup', - 'linkGroupGallery', - ], - - extraDependencies: ['html'], - relations(relation, group) { const relations = {}; diff --git a/src/content/dependencies/linkGroupGallery.js b/src/content/dependencies/linkGroupGallery.js index 86c4a0f3..957756d8 100644 --- a/src/content/dependencies/linkGroupGallery.js +++ b/src/content/dependencies/linkGroupGallery.js @@ -1,6 +1,4 @@ export default { - contentDependencies: ['linkThing'], - relations: (relation, group) => ({link: relation('linkThing', 'localized.groupGallery', group)}), diff --git a/src/content/dependencies/linkListing.js b/src/content/dependencies/linkListing.js index ac66919a..4eb2dce6 100644 --- a/src/content/dependencies/linkListing.js +++ b/src/content/dependencies/linkListing.js @@ -1,7 +1,4 @@ export default { - contentDependencies: ['linkThing'], - extraDependencies: ['language'], - relations: (relation, listing) => ({link: relation('linkThing', 'localized.listing', listing)}), diff --git a/src/content/dependencies/linkListingIndex.js b/src/content/dependencies/linkListingIndex.js index 1bfaf46e..209066a9 100644 --- a/src/content/dependencies/linkListingIndex.js +++ b/src/content/dependencies/linkListingIndex.js @@ -1,6 +1,4 @@ export default { - contentDependencies: ['linkStationaryIndex'], - relations: (relation) => ({link: relation( diff --git a/src/content/dependencies/linkNewsEntry.js b/src/content/dependencies/linkNewsEntry.js index 1fb32dd9..9ef7ac0e 100644 --- a/src/content/dependencies/linkNewsEntry.js +++ b/src/content/dependencies/linkNewsEntry.js @@ -1,6 +1,4 @@ export default { - contentDependencies: ['linkThing'], - relations: (relation, newsEntry) => ({link: relation('linkThing', 'localized.newsEntry', newsEntry)}), diff --git a/src/content/dependencies/linkNewsIndex.js b/src/content/dependencies/linkNewsIndex.js index e911a384..4414afc6 100644 --- a/src/content/dependencies/linkNewsIndex.js +++ b/src/content/dependencies/linkNewsIndex.js @@ -1,6 +1,4 @@ export default { - contentDependencies: ['linkStationaryIndex'], - relations: (relation) => ({link: relation( diff --git a/src/content/dependencies/linkOtherReleaseOnArtistInfoPage.js b/src/content/dependencies/linkOtherReleaseOnArtistInfoPage.js index ec856631..5a16256e 100644 --- a/src/content/dependencies/linkOtherReleaseOnArtistInfoPage.js +++ b/src/content/dependencies/linkOtherReleaseOnArtistInfoPage.js @@ -3,9 +3,6 @@ import {sortAlbumsTracksChronologically, sortContributionsChronologically} import {chunkArtistTrackContributions} from '#wiki-data'; export default { - contentDependencies: ['generateColorStyleAttribute'], - extraDependencies: ['html', 'language'], - query(track, artist) { const relevantInfoPageChunkingContributions = track.allReleases diff --git a/src/content/dependencies/linkPathFromMedia.js b/src/content/dependencies/linkPathFromMedia.js index d71c69f8..344b7d2c 100644 --- a/src/content/dependencies/linkPathFromMedia.js +++ b/src/content/dependencies/linkPathFromMedia.js @@ -1,17 +1,6 @@ import {empty} from '#sugar'; export default { - contentDependencies: ['linkTemplate'], - - extraDependencies: [ - 'checkIfImagePathHasCachedThumbnails', - 'getDimensionsOfImagePath', - 'getSizeOfMediaFile', - 'getThumbnailsAvailableForDimensions', - 'html', - 'to', - ], - relations: (relation) => ({link: relation('linkTemplate')}), diff --git a/src/content/dependencies/linkPathFromRoot.js b/src/content/dependencies/linkPathFromRoot.js index dab3ac1f..b4a90c07 100644 --- a/src/content/dependencies/linkPathFromRoot.js +++ b/src/content/dependencies/linkPathFromRoot.js @@ -1,6 +1,4 @@ export default { - contentDependencies: ['linkTemplate'], - relations: (relation) => ({link: relation('linkTemplate')}), diff --git a/src/content/dependencies/linkPathFromSite.js b/src/content/dependencies/linkPathFromSite.js index 64676465..67a43059 100644 --- a/src/content/dependencies/linkPathFromSite.js +++ b/src/content/dependencies/linkPathFromSite.js @@ -1,6 +1,4 @@ export default { - contentDependencies: ['linkTemplate'], - relations: (relation) => ({link: relation('linkTemplate')}), diff --git a/src/content/dependencies/linkReferencedArtworks.js b/src/content/dependencies/linkReferencedArtworks.js index c456b808..f8b3f3c8 100644 --- a/src/content/dependencies/linkReferencedArtworks.js +++ b/src/content/dependencies/linkReferencedArtworks.js @@ -1,21 +1,9 @@ -import Thing from '#thing'; - export default { - contentDependencies: [ - 'linkAlbumReferencedArtworks', - 'linkTrackReferencedArtworks', - ], - - query: (artwork) => ({ - referenceType: - artwork.thing.constructor[Thing.referenceType], - }), - - relations: (relation, query, artwork) => ({ + relations: (relation, artwork) => ({ link: - (query.referenceType === 'album' + (artwork.thing.isAlbum ? relation('linkAlbumReferencedArtworks', artwork.thing) - : query.referenceType === 'track' + : artwork.thing.isTrack ? relation('linkTrackReferencedArtworks', artwork.thing) : null), }), diff --git a/src/content/dependencies/linkReferencingArtworks.js b/src/content/dependencies/linkReferencingArtworks.js index 0cfca4db..6b7e4f9a 100644 --- a/src/content/dependencies/linkReferencingArtworks.js +++ b/src/content/dependencies/linkReferencingArtworks.js @@ -1,21 +1,9 @@ -import Thing from '#thing'; - export default { - contentDependencies: [ - 'linkAlbumReferencingArtworks', - 'linkTrackReferencingArtworks', - ], - - query: (artwork) => ({ - referenceType: - artwork.thing.constructor[Thing.referenceType], - }), - - relations: (relation, query, artwork) => ({ + relations: (relation, artwork) => ({ link: - (query.referenceType === 'album' + (artwork.thing.isAlbum ? relation('linkAlbumReferencingArtworks', artwork.thing) - : query.referenceType === 'track' + : artwork.thing.isTrack ? relation('linkTrackReferencingArtworks', artwork.thing) : null), }), diff --git a/src/content/dependencies/linkStaticPage.js b/src/content/dependencies/linkStaticPage.js index 032af6c9..c3ac69fa 100644 --- a/src/content/dependencies/linkStaticPage.js +++ b/src/content/dependencies/linkStaticPage.js @@ -1,6 +1,4 @@ export default { - contentDependencies: ['linkThing'], - relations: (relation, staticPage) => ({link: relation('linkThing', 'localized.staticPage', staticPage)}), diff --git a/src/content/dependencies/linkStationaryIndex.js b/src/content/dependencies/linkStationaryIndex.js index d5506e60..10f8ba44 100644 --- a/src/content/dependencies/linkStationaryIndex.js +++ b/src/content/dependencies/linkStationaryIndex.js @@ -1,9 +1,6 @@ // Not to be confused with "html.Stationery". export default { - contentDependencies: ['linkTemplate'], - extraDependencies: ['language'], - relations(relation) { return { linkTemplate: relation('linkTemplate'), diff --git a/src/content/dependencies/linkTemplate.js b/src/content/dependencies/linkTemplate.js index 4f853dc4..10466b43 100644 --- a/src/content/dependencies/linkTemplate.js +++ b/src/content/dependencies/linkTemplate.js @@ -3,13 +3,6 @@ import {empty} from '#sugar'; import striptags from 'striptags'; export default { - extraDependencies: [ - 'appendIndexHTML', - 'html', - 'language', - 'to', - ], - slots: { href: {type: 'string'}, path: {validate: v => v.validateArrayItems(v.isString)}, diff --git a/src/content/dependencies/linkThing.js b/src/content/dependencies/linkThing.js index 3902f380..166a857d 100644 --- a/src/content/dependencies/linkThing.js +++ b/src/content/dependencies/linkThing.js @@ -1,13 +1,4 @@ export default { - contentDependencies: [ - 'generateColorStyleAttribute', - 'generateTextWithTooltip', - 'generateTooltip', - 'linkTemplate', - ], - - extraDependencies: ['html', 'language'], - relations: (relation, _pathKey, thing) => ({ linkTemplate: relation('linkTemplate'), @@ -20,11 +11,15 @@ export default { tooltip: relation('generateTooltip'), + + name: + relation('generateName', thing), }), data: (pathKey, thing) => ({ name: thing.name, nameShort: thing.nameShort ?? thing.shortName, + nameText: thing.nameText, path: (pathKey @@ -75,22 +70,21 @@ export default { hash: {type: 'string'}, }, - generate(data, relations, slots, {html, language}) { + generate(data, relations, slots, {html}) { const path = slots.path ?? data.path; const linkAttributes = slots.attributes; const wrapperAttributes = html.attributes(); - const showShortName = - (slots.preferShortName - ? data.nameShort && data.nameShort !== data.name - : false); - const name = - (showShortName - ? data.nameShort - : data.name); + relations.name.slot('preferShortName', slots.preferShortName); + + const showShortName = + slots.preferShortName && + !data.nameText && + data.nameShort && + data.nameShort !== data.name; const showWikiTooltip = (slots.tooltipStyle === 'auto' @@ -114,7 +108,7 @@ export default { const content = (html.isBlank(slots.content) - ? language.sanitize(name) + ? name : slots.content); if (slots.color !== false) { diff --git a/src/content/dependencies/linkTrack.js b/src/content/dependencies/linkTrack.js index d5d96726..8ee715f0 100644 --- a/src/content/dependencies/linkTrack.js +++ b/src/content/dependencies/linkTrack.js @@ -1,6 +1,4 @@ export default { - contentDependencies: ['linkThing'], - relations: (relation, track) => ({link: relation('linkThing', 'localized.track', track)}), diff --git a/src/content/dependencies/linkTrackDynamically.js b/src/content/dependencies/linkTrackDynamically.js index bbcf1c34..088bbe09 100644 --- a/src/content/dependencies/linkTrackDynamically.js +++ b/src/content/dependencies/linkTrackDynamically.js @@ -1,9 +1,6 @@ import {empty} from '#sugar'; export default { - contentDependencies: ['linkTrack'], - extraDependencies: ['pagePath'], - relations: (relation, track) => ({ infoLink: relation('linkTrack', track), }), diff --git a/src/content/dependencies/linkTrackReferencedArtworks.js b/src/content/dependencies/linkTrackReferencedArtworks.js index b4cb08fe..6da6504e 100644 --- a/src/content/dependencies/linkTrackReferencedArtworks.js +++ b/src/content/dependencies/linkTrackReferencedArtworks.js @@ -1,6 +1,4 @@ export default { - contentDependencies: ['linkThing'], - relations: (relation, track) => ({link: relation('linkThing', 'localized.trackReferencedArtworks', track)}), diff --git a/src/content/dependencies/linkTrackReferencingArtworks.js b/src/content/dependencies/linkTrackReferencingArtworks.js index c9c9f4d1..4d113ba7 100644 --- a/src/content/dependencies/linkTrackReferencingArtworks.js +++ b/src/content/dependencies/linkTrackReferencingArtworks.js @@ -1,6 +1,4 @@ export default { - contentDependencies: ['linkThing'], - relations: (relation, track) => ({link: relation('linkThing', 'localized.trackReferencingArtworks', track)}), diff --git a/src/content/dependencies/linkWikiHomepage.js b/src/content/dependencies/linkWikiHomepage.js index d8d3d0a0..91fbe410 100644 --- a/src/content/dependencies/linkWikiHomepage.js +++ b/src/content/dependencies/linkWikiHomepage.js @@ -1,7 +1,4 @@ export default { - contentDependencies: ['linkTemplate'], - extraDependencies: ['wikiData'], - sprawl({wikiInfo}) { return {wikiShortName: wikiInfo.nameShort}; }, diff --git a/src/content/dependencies/listAlbumsByDate.js b/src/content/dependencies/listAlbumsByDate.js index c83ffc97..eaf9eecf 100644 --- a/src/content/dependencies/listAlbumsByDate.js +++ b/src/content/dependencies/listAlbumsByDate.js @@ -2,9 +2,6 @@ import {sortChronologically} from '#sort'; import {stitchArrays} from '#sugar'; export default { - contentDependencies: ['generateListingPage', 'linkAlbum'], - extraDependencies: ['language', 'wikiData'], - sprawl({albumData}) { return {albumData}; }, diff --git a/src/content/dependencies/listAlbumsByDateAdded.js b/src/content/dependencies/listAlbumsByDateAdded.js index d462ad46..940da67d 100644 --- a/src/content/dependencies/listAlbumsByDateAdded.js +++ b/src/content/dependencies/listAlbumsByDateAdded.js @@ -2,9 +2,6 @@ import {sortAlphabetically} from '#sort'; import {chunkByProperties} from '#sugar'; export default { - contentDependencies: ['generateListingPage', 'linkAlbum'], - extraDependencies: ['language', 'wikiData'], - sprawl({albumData}) { return {albumData}; }, diff --git a/src/content/dependencies/listAlbumsByDuration.js b/src/content/dependencies/listAlbumsByDuration.js index c60685ab..8de2bb84 100644 --- a/src/content/dependencies/listAlbumsByDuration.js +++ b/src/content/dependencies/listAlbumsByDuration.js @@ -3,16 +3,17 @@ import {filterByCount, stitchArrays} from '#sugar'; import {getTotalDuration} from '#wiki-data'; export default { - contentDependencies: ['generateListingPage', 'linkAlbum'], - extraDependencies: ['language', 'wikiData'], - sprawl({albumData}) { return {albumData}; }, query({albumData}, spec) { - const albums = sortAlphabetically(albumData.slice()); - const durations = albums.map(album => getTotalDuration(album.tracks)); + const albums = + sortAlphabetically( + albumData.filter(album => !album.hideDuration)); + + const durations = + albums.map(album => getTotalDuration(album.tracks)); filterByCount(albums, durations); sortByCount(albums, durations, {greatestFirst: true}); diff --git a/src/content/dependencies/listAlbumsByName.js b/src/content/dependencies/listAlbumsByName.js index 21419537..a7939292 100644 --- a/src/content/dependencies/listAlbumsByName.js +++ b/src/content/dependencies/listAlbumsByName.js @@ -2,9 +2,6 @@ import {sortAlphabetically} from '#sort'; import {stitchArrays} from '#sugar'; export default { - contentDependencies: ['generateListingPage', 'linkAlbum'], - extraDependencies: ['language', 'wikiData'], - sprawl({albumData}) { return {albumData}; }, diff --git a/src/content/dependencies/listAlbumsByTracks.js b/src/content/dependencies/listAlbumsByTracks.js index 798e6c2e..b1f62a82 100644 --- a/src/content/dependencies/listAlbumsByTracks.js +++ b/src/content/dependencies/listAlbumsByTracks.js @@ -2,21 +2,25 @@ import {sortAlphabetically, sortByCount} from '#sort'; import {filterByCount, stitchArrays} from '#sugar'; export default { - contentDependencies: ['generateListingPage', 'linkAlbum'], - extraDependencies: ['language', 'wikiData'], - sprawl({albumData}) { return {albumData}; }, query({albumData}, spec) { - const albums = sortAlphabetically(albumData.slice()); - const counts = albums.map(album => album.tracks.length); + const albums = + sortAlphabetically( + albumData.filter(album => !album.hideDuration)); + + const counts = + albums.map(album => album.tracks.length); filterByCount(albums, counts); sortByCount(albums, counts, {greatestFirst: true}); - return {spec, albums, counts}; + const styles = + albums.map(album => album.style); + + return {spec, albums, counts, styles}; }, relations(relation, query) { @@ -32,6 +36,7 @@ export default { data(query) { return { counts: query.counts, + styles: query.styles, }; }, @@ -42,10 +47,19 @@ export default { stitchArrays({ link: relations.albumLinks, count: data.counts, - }).map(({link, count}) => ({ - album: link, - tracks: language.countTracks(count, {unit: true}), - })), + style: data.styles, + }).map(({link, count, style}) => { + const row = { + album: link, + tracks: language.countTracks(count, {unit: true}), + }; + + if (style === 'single') { + row.stringsKey = 'single'; + } + + return row; + }), }); }, }; diff --git a/src/content/dependencies/listAllAdditionalFiles.js b/src/content/dependencies/listAllAdditionalFiles.js index a6e34b9a..2d338916 100644 --- a/src/content/dependencies/listAllAdditionalFiles.js +++ b/src/content/dependencies/listAllAdditionalFiles.js @@ -1,6 +1,4 @@ export default { - contentDependencies: ['listAllAdditionalFilesTemplate'], - relations: (relation, spec) => ({page: relation('listAllAdditionalFilesTemplate', spec, 'additionalFiles')}), diff --git a/src/content/dependencies/listAllAdditionalFilesTemplate.js b/src/content/dependencies/listAllAdditionalFilesTemplate.js index e33ad7b5..f298233c 100644 --- a/src/content/dependencies/listAllAdditionalFilesTemplate.js +++ b/src/content/dependencies/listAllAdditionalFilesTemplate.js @@ -1,209 +1,37 @@ import {sortChronologically} from '#sort'; -import {empty, filterMultipleArrays, stitchArrays} from '#sugar'; export default { - contentDependencies: [ - 'generateListingPage', - 'generateListAllAdditionalFilesChunk', - 'linkAlbum', - 'linkTrack', - 'linkAlbumAdditionalFile', - ], - - extraDependencies: ['html', 'language', 'wikiData'], - sprawl: ({albumData}) => ({albumData}), - query(sprawl, spec, property) { - const albums = - sortChronologically(sprawl.albumData.slice()); - - const tracks = - albums - .map(album => album.tracks.slice()); - - // Get additional file objects from albums and their tracks. - // There's a possibility that albums and tracks don't both implement - // the same additional file fields - in this case, just treat them - // as though they do implement those fields, but don't have any - // additional files of that type. - - const albumAdditionalFileObjects = - albums - .map(album => album[property] ?? []); - - const trackAdditionalFileObjects = - tracks - .map(byAlbum => byAlbum - .map(track => track[property] ?? [])); - - // Filter out tracks that don't have any additional files. - - stitchArrays({tracks, trackAdditionalFileObjects}) - .forEach(({tracks, trackAdditionalFileObjects}) => { - filterMultipleArrays(tracks, trackAdditionalFileObjects, - (track, trackAdditionalFileObjects) => !empty(trackAdditionalFileObjects)); - }); - - // Filter out albums that don't have any tracks, - // nor any additional files of their own. - - filterMultipleArrays(albums, albumAdditionalFileObjects, tracks, trackAdditionalFileObjects, - (album, albumAdditionalFileObjects, tracks, trackAdditionalFileObjects) => - !empty(albumAdditionalFileObjects) || - !empty(trackAdditionalFileObjects)); - - // Map additional file objects into titles and lists of file names. - - const albumAdditionalFileTitles = - albumAdditionalFileObjects - .map(byAlbum => byAlbum - .map(({title}) => title)); - - const albumAdditionalFileFiles = - albumAdditionalFileObjects - .map(byAlbum => byAlbum - .map(({files}) => files ?? [])); - - const trackAdditionalFileTitles = - trackAdditionalFileObjects - .map(byAlbum => byAlbum - .map(byTrack => byTrack - .map(({title}) => title))); + query: (sprawl, spec, property) => ({ + spec, + property, - const trackAdditionalFileFiles = - trackAdditionalFileObjects - .map(byAlbum => byAlbum - .map(byTrack => byTrack - .map(({files}) => files ?? []))); - - return { - spec, - albums, - tracks, - albumAdditionalFileTitles, - albumAdditionalFileFiles, - trackAdditionalFileTitles, - trackAdditionalFileFiles, - }; - }, + albums: + sortChronologically(sprawl.albumData.slice()), + }), relations: (relation, query) => ({ page: relation('generateListingPage', query.spec), - albumLinks: - query.albums - .map(album => relation('linkAlbum', album)), - - trackLinks: - query.tracks - .map(byAlbum => byAlbum - .map(track => relation('linkTrack', track))), - - albumChunks: - query.albums - .map(() => relation('generateListAllAdditionalFilesChunk')), - - trackChunks: - query.tracks - .map(byAlbum => byAlbum - .map(() => relation('generateListAllAdditionalFilesChunk'))), - - albumAdditionalFileLinks: - stitchArrays({ - album: query.albums, - files: query.albumAdditionalFileFiles, - }).map(({album, files: byAlbum}) => - byAlbum.map(files => files - .map(file => - relation('linkAlbumAdditionalFile', album, file)))), - - trackAdditionalFileLinks: - stitchArrays({ - album: query.albums, - files: query.trackAdditionalFileFiles, - }).map(({album, files: byAlbum}) => - byAlbum - .map(byTrack => byTrack - .map(files => files - .map(file => relation('linkAlbumAdditionalFile', album, file))))), - }), - - data: (query) => ({ - albumAdditionalFileTitles: query.albumAdditionalFileTitles, - trackAdditionalFileTitles: query.trackAdditionalFileTitles, - albumAdditionalFileFiles: query.albumAdditionalFileFiles, - trackAdditionalFileFiles: query.trackAdditionalFileFiles, + albumSections: + query.albums.map(album => + relation('generateListAllAdditionalFilesAlbumSection', + album, + query.property)), }), slots: { stringsKey: {type: 'string'}, }, - generate: (data, relations, slots, {html, language}) => + generate: (relations, slots) => relations.page.slots({ type: 'custom', content: - stitchArrays({ - albumLink: relations.albumLinks, - trackLinks: relations.trackLinks, - albumChunk: relations.albumChunks, - trackChunks: relations.trackChunks, - albumAdditionalFileTitles: data.albumAdditionalFileTitles, - trackAdditionalFileTitles: data.trackAdditionalFileTitles, - albumAdditionalFileLinks: relations.albumAdditionalFileLinks, - trackAdditionalFileLinks: relations.trackAdditionalFileLinks, - albumAdditionalFileFiles: data.albumAdditionalFileFiles, - trackAdditionalFileFiles: data.trackAdditionalFileFiles, - }).map(({ - albumLink, - trackLinks, - albumChunk, - trackChunks, - albumAdditionalFileTitles, - trackAdditionalFileTitles, - albumAdditionalFileLinks, - trackAdditionalFileLinks, - albumAdditionalFileFiles, - trackAdditionalFileFiles, - }) => [ - html.tag('h3', {class: 'content-heading'}, albumLink), - - html.tag('dl', [ - albumChunk.slots({ - title: - language.$('listingPage', slots.stringsKey, 'albumFiles'), - - additionalFileTitles: albumAdditionalFileTitles, - additionalFileLinks: albumAdditionalFileLinks, - additionalFileFiles: albumAdditionalFileFiles, - - stringsKey: slots.stringsKey, - }), - - stitchArrays({ - trackLink: trackLinks, - trackChunk: trackChunks, - trackAdditionalFileTitles, - trackAdditionalFileLinks, - trackAdditionalFileFiles, - }).map(({ - trackLink, - trackChunk, - trackAdditionalFileTitles, - trackAdditionalFileLinks, - trackAdditionalFileFiles, - }) => - trackChunk.slots({ - title: trackLink, - additionalFileTitles: trackAdditionalFileTitles, - additionalFileLinks: trackAdditionalFileLinks, - additionalFileFiles: trackAdditionalFileFiles, - stringsKey: slots.stringsKey, - })), - ]), - ]), + relations.albumSections.map(section => + section.slot('stringsKey', slots.stringsKey)), }), }; diff --git a/src/content/dependencies/listAllMidiProjectFiles.js b/src/content/dependencies/listAllMidiProjectFiles.js index 31a70ef0..109cf2e7 100644 --- a/src/content/dependencies/listAllMidiProjectFiles.js +++ b/src/content/dependencies/listAllMidiProjectFiles.js @@ -1,6 +1,4 @@ export default { - contentDependencies: ['listAllAdditionalFilesTemplate'], - relations: (relation, spec) => ({page: relation('listAllAdditionalFilesTemplate', spec, 'midiProjectFiles')}), diff --git a/src/content/dependencies/listAllSheetMusicFiles.js b/src/content/dependencies/listAllSheetMusicFiles.js index 166b2068..4f3bdb96 100644 --- a/src/content/dependencies/listAllSheetMusicFiles.js +++ b/src/content/dependencies/listAllSheetMusicFiles.js @@ -1,6 +1,4 @@ export default { - contentDependencies: ['listAllAdditionalFilesTemplate'], - relations: (relation, spec) => ({page: relation('listAllAdditionalFilesTemplate', spec, 'sheetMusicFiles')}), diff --git a/src/content/dependencies/listArtTagNetwork.js b/src/content/dependencies/listArtTagNetwork.js index 93dd4ce8..98f81019 100644 --- a/src/content/dependencies/listArtTagNetwork.js +++ b/src/content/dependencies/listArtTagNetwork.js @@ -2,9 +2,6 @@ import {sortAlphabetically} from '#sort'; import {empty, stitchArrays, unique} from '#sugar'; export default { - contentDependencies: ['generateListingPage', 'linkArtTagInfo'], - extraDependencies: ['html', 'language', 'wikiData'], - sprawl({artTagData}) { return {artTagData}; }, diff --git a/src/content/dependencies/listArtTagsByName.js b/src/content/dependencies/listArtTagsByName.js index 1df9dfff..10e9e873 100644 --- a/src/content/dependencies/listArtTagsByName.js +++ b/src/content/dependencies/listArtTagsByName.js @@ -2,9 +2,6 @@ import {sortAlphabetically} from '#sort'; import {stitchArrays, unique} from '#sugar'; export default { - contentDependencies: ['generateListingPage', 'linkArtTagGallery'], - extraDependencies: ['language', 'wikiData'], - sprawl({artTagData}) { return {artTagData}; }, diff --git a/src/content/dependencies/listArtTagsByUses.js b/src/content/dependencies/listArtTagsByUses.js index eca7f1c6..5131580f 100644 --- a/src/content/dependencies/listArtTagsByUses.js +++ b/src/content/dependencies/listArtTagsByUses.js @@ -2,9 +2,6 @@ import {sortAlphabetically, sortByCount} from '#sort'; import {filterByCount, stitchArrays, unique} from '#sugar'; export default { - contentDependencies: ['generateListingPage', 'linkArtTagGallery'], - extraDependencies: ['language', 'wikiData'], - sprawl: ({artTagData}) => ({artTagData}), diff --git a/src/content/dependencies/listArtistsByCommentaryEntries.js b/src/content/dependencies/listArtistsByCommentaryEntries.js index eff2dba3..ab7bde6c 100644 --- a/src/content/dependencies/listArtistsByCommentaryEntries.js +++ b/src/content/dependencies/listArtistsByCommentaryEntries.js @@ -2,9 +2,6 @@ import {sortAlphabetically, sortByCount} from '#sort'; import {filterByCount, stitchArrays} from '#sugar'; export default { - contentDependencies: ['generateListingPage', 'linkArtist'], - extraDependencies: ['language', 'wikiData'], - sprawl({artistData}) { return {artistData}; }, diff --git a/src/content/dependencies/listArtistsByContributions.js b/src/content/dependencies/listArtistsByContributions.js index 41944959..2f8d6391 100644 --- a/src/content/dependencies/listArtistsByContributions.js +++ b/src/content/dependencies/listArtistsByContributions.js @@ -1,18 +1,8 @@ import {sortAlphabetically, sortByCount} from '#sort'; - -import { - accumulateSum, - empty, - filterByCount, - filterMultipleArrays, - stitchArrays, - unique, -} from '#sugar'; +import {empty, filterByCount, filterMultipleArrays, stitchArrays} + from '#sugar'; export default { - contentDependencies: ['generateListingPage', 'linkArtist'], - extraDependencies: ['html', 'language', 'wikiData'], - sprawl({artistData, wikiInfo}) { return { artistData, @@ -41,37 +31,46 @@ export default { query[countsKey] = counts; }; + const countContributions = (artist, keys) => { + const contribs = + keys + .flatMap(key => artist[key]) + .filter(contrib => contrib.countInContributionTotals); + + const things = + new Set(contribs.map(contrib => contrib.thing)); + + return things.size; + }; + queryContributionInfo( 'artistsByTrackContributions', 'countsByTrackContributions', artist => - (unique( - ([ - artist.trackArtistContributions, - artist.trackContributorContributions, - ]).flat() - .map(({thing}) => thing) - )).length); + countContributions(artist, [ + 'trackArtistContributions', + 'trackContributorContributions', + ])); queryContributionInfo( 'artistsByArtworkContributions', 'countsByArtworkContributions', artist => - accumulateSum( - [ - artist.albumCoverArtistContributions, - artist.albumWallpaperArtistContributions, - artist.albumBannerArtistContributions, - artist.trackCoverArtistContributions, - ], - contribs => contribs.length)); + countContributions(artist, [ + 'albumCoverArtistContributions', + 'albumWallpaperArtistContributions', + 'albumBannerArtistContributions', + 'trackCoverArtistContributions', + ])); if (sprawl.enableFlashesAndGames) { queryContributionInfo( 'artistsByFlashContributions', 'countsByFlashContributions', artist => - artist.flashContributorContributions.length); + countContributions(artist, [ + 'flashContributorContributions', + ])); } return query; diff --git a/src/content/dependencies/listArtistsByDuration.js b/src/content/dependencies/listArtistsByDuration.js index 6b2a18a0..1d550b26 100644 --- a/src/content/dependencies/listArtistsByDuration.js +++ b/src/content/dependencies/listArtistsByDuration.js @@ -2,9 +2,6 @@ import {sortAlphabetically, sortByCount} from '#sort'; import {filterByCount, stitchArrays} from '#sugar'; export default { - contentDependencies: ['generateListingPage', 'linkArtist'], - extraDependencies: ['language', 'wikiData'], - sprawl({artistData}) { return {artistData}; }, diff --git a/src/content/dependencies/listArtistsByGroup.js b/src/content/dependencies/listArtistsByGroup.js index 17096cfc..44564b4b 100644 --- a/src/content/dependencies/listArtistsByGroup.js +++ b/src/content/dependencies/listArtistsByGroup.js @@ -10,9 +10,6 @@ import { } from '#sugar'; export default { - contentDependencies: ['generateListingPage', 'linkArtist', 'linkGroup'], - extraDependencies: ['language', 'wikiData'], - sprawl({artistData, wikiInfo}) { return {artistData, wikiInfo}; }, diff --git a/src/content/dependencies/listArtistsByLatestContribution.js b/src/content/dependencies/listArtistsByLatestContribution.js index 2a8d1b4c..dc7341cf 100644 --- a/src/content/dependencies/listArtistsByLatestContribution.js +++ b/src/content/dependencies/listArtistsByLatestContribution.js @@ -11,15 +11,6 @@ import { const {Album, Flash} = T; export default { - contentDependencies: [ - 'generateListingPage', - 'linkAlbum', - 'linkArtist', - 'linkFlash', - ], - - extraDependencies: ['html', 'language', 'wikiData'], - sprawl: ({albumData, artistData, flashData, trackData, wikiInfo}) => ({albumData, artistData, flashData, trackData, enableFlashesAndGames: wikiInfo.enableFlashesAndGames}), diff --git a/src/content/dependencies/listArtistsByName.js b/src/content/dependencies/listArtistsByName.js index 93218492..8bee4947 100644 --- a/src/content/dependencies/listArtistsByName.js +++ b/src/content/dependencies/listArtistsByName.js @@ -3,9 +3,6 @@ import {stitchArrays} from '#sugar'; import {getArtistNumContributions} from '#wiki-data'; export default { - contentDependencies: ['generateListingPage', 'linkArtist', 'linkGroup'], - extraDependencies: ['language', 'wikiData'], - sprawl: ({artistData, wikiInfo}) => ({artistData, wikiInfo}), diff --git a/src/content/dependencies/listGroupsByAlbums.js b/src/content/dependencies/listGroupsByAlbums.js index 4adfb6d9..64814640 100644 --- a/src/content/dependencies/listGroupsByAlbums.js +++ b/src/content/dependencies/listGroupsByAlbums.js @@ -2,9 +2,6 @@ import {sortAlphabetically, sortByCount} from '#sort'; import {filterByCount, stitchArrays} from '#sugar'; export default { - contentDependencies: ['generateListingPage', 'linkGroup'], - extraDependencies: ['language', 'wikiData'], - sprawl({groupData}) { return {groupData}; }, diff --git a/src/content/dependencies/listGroupsByCategory.js b/src/content/dependencies/listGroupsByCategory.js index 43919bef..4c10a1e4 100644 --- a/src/content/dependencies/listGroupsByCategory.js +++ b/src/content/dependencies/listGroupsByCategory.js @@ -1,9 +1,6 @@ import {stitchArrays} from '#sugar'; export default { - contentDependencies: ['generateListingPage', 'linkGroup', 'linkGroupGallery'], - extraDependencies: ['language', 'wikiData'], - sprawl({groupCategoryData}) { return {groupCategoryData}; }, diff --git a/src/content/dependencies/listGroupsByDuration.js b/src/content/dependencies/listGroupsByDuration.js index c79e1bc4..089915c2 100644 --- a/src/content/dependencies/listGroupsByDuration.js +++ b/src/content/dependencies/listGroupsByDuration.js @@ -3,9 +3,6 @@ import {filterByCount, stitchArrays} from '#sugar'; import {getTotalDuration} from '#wiki-data'; export default { - contentDependencies: ['generateListingPage', 'linkGroup'], - extraDependencies: ['language', 'wikiData'], - sprawl({groupData}) { return {groupData}; }, diff --git a/src/content/dependencies/listGroupsByLatestAlbum.js b/src/content/dependencies/listGroupsByLatestAlbum.js index 48319314..2d83a354 100644 --- a/src/content/dependencies/listGroupsByLatestAlbum.js +++ b/src/content/dependencies/listGroupsByLatestAlbum.js @@ -2,15 +2,6 @@ import {compareDates, sortChronologically} from '#sort'; import {filterMultipleArrays, sortMultipleArrays, stitchArrays} from '#sugar'; export default { - contentDependencies: [ - 'generateListingPage', - 'linkAlbum', - 'linkGroup', - 'linkGroupGallery', - ], - - extraDependencies: ['language', 'wikiData'], - sprawl({groupData}) { return {groupData}; }, diff --git a/src/content/dependencies/listGroupsByName.js b/src/content/dependencies/listGroupsByName.js index 696a49bd..e3308158 100644 --- a/src/content/dependencies/listGroupsByName.js +++ b/src/content/dependencies/listGroupsByName.js @@ -2,9 +2,6 @@ import {sortAlphabetically} from '#sort'; import {stitchArrays} from '#sugar'; export default { - contentDependencies: ['generateListingPage', 'linkGroup', 'linkGroupGallery'], - extraDependencies: ['language', 'wikiData'], - sprawl({groupData}) { return {groupData}; }, diff --git a/src/content/dependencies/listGroupsByTracks.js b/src/content/dependencies/listGroupsByTracks.js index 0b5e4e97..c9d97614 100644 --- a/src/content/dependencies/listGroupsByTracks.js +++ b/src/content/dependencies/listGroupsByTracks.js @@ -2,9 +2,6 @@ import {sortAlphabetically, sortByCount} from '#sort'; import {accumulateSum, filterByCount, stitchArrays} from '#sugar'; export default { - contentDependencies: ['generateListingPage', 'linkGroup'], - extraDependencies: ['language', 'wikiData'], - sprawl({groupData}) { return {groupData}; }, diff --git a/src/content/dependencies/listRandomPageLinks.js b/src/content/dependencies/listRandomPageLinks.js index 79bba441..81eca274 100644 --- a/src/content/dependencies/listRandomPageLinks.js +++ b/src/content/dependencies/listRandomPageLinks.js @@ -2,14 +2,6 @@ import {sortChronologically} from '#sort'; import {empty} from '#sugar'; export default { - contentDependencies: [ - 'generateListingPage', - 'generateListRandomPageLinksAlbumLink', - 'linkGroup', - ], - - extraDependencies: ['html', 'language', 'wikiData'], - sprawl: ({albumData, wikiInfo}) => ({albumData, wikiInfo}), query(sprawl, spec) { diff --git a/src/content/dependencies/listTracksByAlbum.js b/src/content/dependencies/listTracksByAlbum.js index b2405034..f6858ada 100644 --- a/src/content/dependencies/listTracksByAlbum.js +++ b/src/content/dependencies/listTracksByAlbum.js @@ -1,7 +1,4 @@ export default { - contentDependencies: ['generateListingPage', 'linkAlbum', 'linkTrack'], - extraDependencies: ['language', 'wikiData'], - sprawl({albumData}) { return {albumData}; }, diff --git a/src/content/dependencies/listTracksByDate.js b/src/content/dependencies/listTracksByDate.js index dcfaeaf0..9d63f19b 100644 --- a/src/content/dependencies/listTracksByDate.js +++ b/src/content/dependencies/listTracksByDate.js @@ -2,9 +2,6 @@ import {sortAlbumsTracksChronologically} from '#sort'; import {chunkByProperties, stitchArrays} from '#sugar'; export default { - contentDependencies: ['generateListingPage', 'linkAlbum', 'linkTrack'], - extraDependencies: ['language', 'wikiData'], - sprawl: ({trackData}) => ({trackData}), query({trackData}, spec) { diff --git a/src/content/dependencies/listTracksByDuration.js b/src/content/dependencies/listTracksByDuration.js index 64feb4f1..95fd28b2 100644 --- a/src/content/dependencies/listTracksByDuration.js +++ b/src/content/dependencies/listTracksByDuration.js @@ -2,9 +2,6 @@ import {sortAlphabetically, sortByCount} from '#sort'; import {filterByCount, stitchArrays} from '#sugar'; export default { - contentDependencies: ['generateListingPage', 'linkTrack'], - extraDependencies: ['language', 'wikiData'], - sprawl({trackData}) { return {trackData}; }, diff --git a/src/content/dependencies/listTracksByDurationInAlbum.js b/src/content/dependencies/listTracksByDurationInAlbum.js index c1ea32a1..ad44c7b2 100644 --- a/src/content/dependencies/listTracksByDurationInAlbum.js +++ b/src/content/dependencies/listTracksByDurationInAlbum.js @@ -2,9 +2,6 @@ import {sortByCount, sortChronologically} from '#sort'; import {filterByCount, filterMultipleArrays, stitchArrays} from '#sugar'; export default { - contentDependencies: ['generateListingPage', 'linkAlbum', 'linkTrack'], - extraDependencies: ['language', 'wikiData'], - sprawl({albumData}) { return {albumData}; }, diff --git a/src/content/dependencies/listTracksByName.js b/src/content/dependencies/listTracksByName.js index 773b0473..a9c2c504 100644 --- a/src/content/dependencies/listTracksByName.js +++ b/src/content/dependencies/listTracksByName.js @@ -1,9 +1,6 @@ import {sortAlphabetically} from '#sort'; export default { - contentDependencies: ['generateListingPage', 'linkTrack'], - extraDependencies: ['wikiData'], - sprawl({trackData}) { return {trackData}; }, diff --git a/src/content/dependencies/listTracksByTimesReferenced.js b/src/content/dependencies/listTracksByTimesReferenced.js index 5838ded0..8a57e1a6 100644 --- a/src/content/dependencies/listTracksByTimesReferenced.js +++ b/src/content/dependencies/listTracksByTimesReferenced.js @@ -2,9 +2,6 @@ import {sortAlbumsTracksChronologically, sortByCount} from '#sort'; import {filterByCount, stitchArrays} from '#sugar'; export default { - contentDependencies: ['generateListingPage', 'linkTrack'], - extraDependencies: ['language', 'wikiData'], - sprawl({trackData}) { return {trackData}; }, diff --git a/src/content/dependencies/listTracksInFlashesByAlbum.js b/src/content/dependencies/listTracksInFlashesByAlbum.js index 8ca0d993..db5472db 100644 --- a/src/content/dependencies/listTracksInFlashesByAlbum.js +++ b/src/content/dependencies/listTracksInFlashesByAlbum.js @@ -2,9 +2,6 @@ import {sortChronologically} from '#sort'; import {empty, filterMultipleArrays, stitchArrays} from '#sugar'; export default { - contentDependencies: ['generateListingPage', 'linkAlbum', 'linkFlash', 'linkTrack'], - extraDependencies: ['language', 'wikiData'], - sprawl({albumData}) { return {albumData}; }, diff --git a/src/content/dependencies/listTracksInFlashesByFlash.js b/src/content/dependencies/listTracksInFlashesByFlash.js index 6ab954ed..325b3cb5 100644 --- a/src/content/dependencies/listTracksInFlashesByFlash.js +++ b/src/content/dependencies/listTracksInFlashesByFlash.js @@ -2,9 +2,6 @@ import {sortFlashesChronologically} from '#sort'; import {empty, stitchArrays} from '#sugar'; export default { - contentDependencies: ['generateListingPage', 'linkAlbum', 'linkFlash', 'linkTrack'], - extraDependencies: ['wikiData'], - sprawl({flashData}) { return {flashData}; }, diff --git a/src/content/dependencies/listTracksNeedingLyrics.js b/src/content/dependencies/listTracksNeedingLyrics.js new file mode 100644 index 00000000..d21fcd06 --- /dev/null +++ b/src/content/dependencies/listTracksNeedingLyrics.js @@ -0,0 +1,7 @@ +export default { + relations: (relation, spec) => + ({page: relation('listTracksWithExtra', spec, 'needsLyrics', 'truthy')}), + + generate: (relations) => + relations.page, +}; diff --git a/src/content/dependencies/listTracksWithExtra.js b/src/content/dependencies/listTracksWithExtra.js index c7f42f9d..09d8ee21 100644 --- a/src/content/dependencies/listTracksWithExtra.js +++ b/src/content/dependencies/listTracksWithExtra.js @@ -2,9 +2,6 @@ import {sortChronologically} from '#sort'; import {empty, filterMultipleArrays, stitchArrays} from '#sugar'; export default { - contentDependencies: ['generateListingPage', 'linkAlbum', 'linkTrack'], - extraDependencies: ['html', 'language', 'wikiData'], - sprawl({albumData}) { return {albumData}; }, diff --git a/src/content/dependencies/listTracksWithLyrics.js b/src/content/dependencies/listTracksWithLyrics.js index e6ab9d7d..79d76bf3 100644 --- a/src/content/dependencies/listTracksWithLyrics.js +++ b/src/content/dependencies/listTracksWithLyrics.js @@ -1,6 +1,4 @@ export default { - contentDependencies: ['listTracksWithExtra'], - relations: (relation, spec) => ({page: relation('listTracksWithExtra', spec, 'lyrics', 'array')}), diff --git a/src/content/dependencies/listTracksWithMidiProjectFiles.js b/src/content/dependencies/listTracksWithMidiProjectFiles.js index 418af4c2..9a48f6ae 100644 --- a/src/content/dependencies/listTracksWithMidiProjectFiles.js +++ b/src/content/dependencies/listTracksWithMidiProjectFiles.js @@ -1,6 +1,4 @@ export default { - contentDependencies: ['listTracksWithExtra'], - relations: (relation, spec) => ({page: relation('listTracksWithExtra', spec, 'midiProjectFiles', 'array')}), diff --git a/src/content/dependencies/listTracksWithSheetMusicFiles.js b/src/content/dependencies/listTracksWithSheetMusicFiles.js index 0c6761eb..f0ba4196 100644 --- a/src/content/dependencies/listTracksWithSheetMusicFiles.js +++ b/src/content/dependencies/listTracksWithSheetMusicFiles.js @@ -1,6 +1,4 @@ export default { - contentDependencies: ['listTracksWithExtra'], - relations: (relation, spec) => ({page: relation('listTracksWithExtra', spec, 'sheetMusicFiles', 'array')}), diff --git a/src/content/dependencies/transformContent.js b/src/content/dependencies/transformContent.js index 1bbd45e2..db9f5d99 100644 --- a/src/content/dependencies/transformContent.js +++ b/src/content/dependencies/transformContent.js @@ -1,5 +1,8 @@ +import {basename} from 'node:path'; + +import {logWarn} from '#cli'; import {bindFind} from '#find'; -import {replacerSpec, parseInput} from '#replacer'; +import {replacerSpec, parseContentNodes} from '#replacer'; import {Marked} from 'marked'; import striptags from 'striptags'; @@ -32,8 +35,8 @@ const inlineMarked = new Marked({ ...commonMarkedOptions, renderer: { - paragraph(text) { - return text; + paragraph({tokens}) { + return this.parser.parseInline(tokens); }, }, }); @@ -46,26 +49,47 @@ function getPlaceholder(node, content) { return {type: 'text', data: content.slice(node.i, node.iEnd)}; } -export default { - contentDependencies: [ - ...( - Object.values(replacerSpec) - .map(description => description.link) - .filter(Boolean)), - 'image', - 'linkExternal', - ], - - extraDependencies: ['html', 'language', 'to', 'wikiData'], +function getArg(node, argKey) { + return ( + node.data.args + ?.find(({key}) => key.data === argKey) + ?.value ?? + null); +} +export default { sprawl(wikiData, content) { - const find = bindFind(wikiData); + const find = + bindFind(wikiData, { + mode: 'quiet', + fuzz: { + capitalization: true, + kebab: true, + }, + }); - const parsedNodes = parseInput(content ?? ''); + const {result: parsedNodes, error} = + parseContentNodes(content ?? '', {errorMode: 'return'}); return { + error, + nodes: parsedNodes .map(node => { + if (node.type === 'tooltip') { + return { + i: node.i, + iEnd: node.iEnd, + type: 'tooltip', + data: { + // No recursion yet. Sorry! + tooltip: node.data.content[0].data, + label: node.data.label[0].data, + link: null, + }, + }; + } + if (node.type !== 'tag') { return node; } @@ -86,7 +110,7 @@ export default { } if (spec.link) { - let data = {link: spec.link}; + let data = {link: spec.link, replacerKey, replacerValue}; determineData: { // No value at all: this is an index link. @@ -125,15 +149,46 @@ export default { data.label = enteredLabel ?? - (transformName && data.thing.name - ? transformName(data.thing.name, node, content) - : null); + + (transformName && data.thing.name && + replacerKeyImplied && replacerValue === data.thing.name + + ? transformName(data.thing.name, node, content) + : null) ?? + + (replacerKeyImplied + ? replacerValue + : null); data.hash = enteredHash ?? null; return {i: node.i, iEnd: node.iEnd, type: 'internal-link', data}; } + if (replacerKey === 'tooltip') { + // TODO: Again, no recursive nodes. Sorry! + // const enteredLabel = node.data.label && transformNode(node.data.label, opts); + const enteredLabel = node.data.label?.data; + + return { + i: node.i, + iEnd: node.iEnd, + type: 'tooltip', + data: { + tooltip: + replacerValue ?? '(empty tooltip...)', + + label: + enteredLabel ?? '(tooltip without label)', + + link: + (getArg(node, 'link') + ? getArg(node, 'link')[0].data + : null), + }, + }; + } + // This will be another {type: 'tag'} node which gets processed in // generate. Extract replacerKey and replacerValue now, since it'd // be a pain to deal with later. @@ -141,8 +196,8 @@ export default { ...node, data: { ...node.data, - replacerKey: node.data.replacerKey.data, - replacerValue: node.data.replacerValue[0].data, + replacerKey, + replacerValue, }, }; }), @@ -153,25 +208,11 @@ export default { return { content, + error: + sprawl.error, + nodes: - sprawl.nodes - .map(node => { - switch (node.type) { - // Replace internal link nodes with a stub. It'll be replaced - // (by position) with an item from relations. - // - // TODO: This should be where label and hash get passed through, - // rather than in relations... (in which case there's no need to - // handle it specially here, and we can really just return - // data.nodes = sprawl.nodes) - case 'internal-link': - return {type: 'internal-link'}; - - // Other nodes will get processed in generate. - default: - return node; - } - }), + sprawl.nodes, }; }, @@ -191,6 +232,12 @@ export default { : getPlaceholder(node, content)); return { + textWithTooltip: + relation('generateTextWithTooltip'), + + tooltip: + relation('generateTooltip'), + internalLinks: nodes .filter(({type}) => type === 'internal-link') @@ -209,11 +256,15 @@ export default { externalLinks: nodes .filter(({type}) => type === 'external-link') - .map(node => { - const {href} = node.data; + .map(({data: {href}}) => + relation('linkExternal', href)), - return relation('linkExternal', href); - }), + externalLinksForTooltipNodes: + nodes + .filter(({type}) => type === 'tooltip') + .filter(({data}) => data.link) + .map(({data: {link: href}}) => + relation('linkExternal', href)), images: nodes @@ -253,15 +304,54 @@ export default { validate: v => v.is('small', 'medium', 'large'), default: 'large', }, + + substitute: { + validate: v => + v.strictArrayOf( + v.validateProperties({ + match: v.validateProperties({ + replacerKey: v.isString, + replacerValue: v.isString, + }), + + substitute: v.isHTML, + + apply: v.optional(v.isFunction), + })), + }, }, - generate(data, relations, slots, {html, language, to}) { + generate(data, relations, slots, {html, language, niceShowAggregate, to}) { + if (data.error) { + logWarn`Error in content text.`; + niceShowAggregate(data.error); + } + let imageIndex = 0; let internalLinkIndex = 0; let externalLinkIndex = 0; + let externalLinkForTooltipNodeIndex = 0; let offsetTextNode = 0; + const substitutions = + (slots.substitute + ? slots.substitute.slice() + : []); + + const pickSubstitution = node => { + const index = + substitutions.findIndex(({match}) => + match.replacerKey === node.data.replacerKey && + match.replacerValue === node.data.replacerValue); + + if (index === -1) { + return null; + } + + return substitutions.splice(index, 1).at(0); + }; + const contentFromNodes = data.nodes.map((node, index) => { const nextNode = data.nodes[index + 1]; @@ -280,6 +370,25 @@ export default { } }; + const substitution = pickSubstitution(node); + + if (substitution) { + const source = + substitution.substitute; + + let substitute = source; + + if (substitution.apply) { + const result = substitution.apply(source, node); + + if (result !== undefined) { + substitute = result; + } + } + + return {type: 'substitution', data: substitute}; + } + switch (node.type) { case 'text': { const text = node.data.slice(offsetTextNode); @@ -313,9 +422,8 @@ export default { height && {height}, style && {style}, - align === 'center' && - !link && - {class: 'align-center'}, + align && !link && + {class: 'align-' + align}, pixelate && {class: 'pixelate'}); @@ -326,8 +434,8 @@ export default { {href: link}, {target: '_blank'}, - align === 'center' && - {class: 'align-center'}, + align && + {class: 'align-' + align}, {title: language.encapsulate('misc.external.opensInNewTab', capsule => @@ -377,8 +485,8 @@ export default { inline: false, data: html.tag('div', {class: 'content-image-container'}, - align === 'center' && - {class: 'align-center'}, + align && + {class: 'align-' + align}, image), }; @@ -390,22 +498,31 @@ export default { ? to('media.path', node.src.slice('media/'.length)) : node.src); - const {width, height, align, pixelate} = node; + const {width, height, align, inline, pixelate} = node; - const content = - html.tag('div', {class: 'content-video-container'}, - align === 'center' && - {class: 'align-center'}, + const video = + html.tag('video', + src && {src}, + width && {width}, + height && {height}, - html.tag('video', - src && {src}, - width && {width}, - height && {height}, + {controls: true}, - {controls: true}, + align && inline && + {class: 'align-' + align}, + + pixelate && + {class: 'pixelate'}); + + const content = + (inline + ? video + : html.tag('div', {class: 'content-video-container'}, + align && + {class: 'align-' + align}, + + video)); - pixelate && - {class: 'pixelate'})); return { type: 'processed-video', @@ -419,15 +536,14 @@ export default { ? to('media.path', node.src.slice('media/'.length)) : node.src); - const {align, inline} = node; + const {align, inline, nameless} = node; const audio = html.tag('audio', src && {src}, - align === 'center' && - inline && - {class: 'align-center'}, + align && inline && + {class: 'align-' + align}, {controls: true}); @@ -435,10 +551,17 @@ export default { (inline ? audio : html.tag('div', {class: 'content-audio-container'}, - align === 'center' && - {class: 'align-center'}, + align && + {class: 'align-' + align}, + + [ + !nameless && + html.tag('a', {class: 'filename'}, + src && {href: src}, + language.sanitize(basename(node.src))), - audio)); + audio, + ])); return { type: 'processed-audio', @@ -484,7 +607,7 @@ export default { try { link.getSlotDescription('preferShortName'); hasPreferShortNameSlot = true; - } catch (error) { + } catch { hasPreferShortNameSlot = false; } @@ -497,7 +620,7 @@ export default { try { link.getSlotDescription('tooltipStyle'); hasTooltipStyleSlot = true; - } catch (error) { + } catch { hasTooltipStyleSlot = false; } @@ -521,9 +644,12 @@ export default { } case 'external-link': { - const {label} = node.data; const externalLink = relations.externalLinks[externalLinkIndex++]; + const label = + node.data.label ?? + node.data.href.replace(/^https?:\/\//, ''); + if (slots.textOnly) { return {type: 'text', data: label}; } @@ -548,6 +674,52 @@ export default { return {type: 'processed-external-link', data: externalLink}; } + case 'tooltip': { + const {label, link, tooltip: tooltipContent} = node.data; + + const externalLink = + (link + ? relations.externalLinksForTooltipNodes + .at(externalLinkForTooltipNodeIndex++) + : null); + + if (externalLink) { + externalLink.setSlots({ + content: label, + fromContent: true, + }); + + if (slots.indicateExternalLinks) { + externalLink.setSlots({ + indicateExternal: true, + disableBrowserTooltip: true, + tab: 'separate', + style: 'platform', + }); + } + } + + const textWithTooltip = relations.textWithTooltip.clone(); + const tooltip = relations.tooltip.clone(); + + tooltip.setSlots({ + attributes: {class: 'content-tooltip'}, + content: tooltipContent, // Not sanitized! + }); + + textWithTooltip.setSlots({ + attributes: [ + {class: 'content-tooltip-guy'}, + externalLink && {class: 'has-link'}, + ], + + text: externalLink ?? label, + tooltip, + }); + + return {type: 'processed-tooltip', data: textWithTooltip}; + } + case 'tag': { const {replacerKey, replacerValue} = node.data; @@ -699,8 +871,8 @@ export default { extractNonTextNodes() // Compress multiple line breaks into single line breaks, // except when they're preceding or following indented - // text (by at least two spaces). - .replace(/(?<! .*)\n{2,}(?!^ )/gm, '\n') /* eslint-disable-line no-regex-spaces */ + // text (by at least two spaces) or blockquotes. + .replace(/(?<!^ .*|^>.*)\n{2,}(?!^ |^>)/gm, '\n') /* eslint-disable-line no-regex-spaces */ // Expand line breaks which don't follow a list, quote, // or <br> / " ", and which don't precede or follow // indented text (by at least two spaces). diff --git a/src/data/cacheable-object.js b/src/data/cacheable-object.js index a089e325..3f70af30 100644 --- a/src/data/cacheable-object.js +++ b/src/data/cacheable-object.js @@ -12,6 +12,7 @@ export default class CacheableObject { static propertyDependants = Symbol.for('CacheableObject.propertyDependants'); static cacheValid = Symbol.for('CacheableObject.cacheValid'); + static cachedValue = Symbol.for('CacheableObject.cachedValue'); static updateValue = Symbol.for('CacheableObject.updateValues'); constructor({seal = true} = {}) { @@ -243,13 +244,13 @@ export class CacheableObjectPropertyValueError extends Error { try { inspectOldValue = inspect(oldValue); - } catch (error) { + } catch { inspectOldValue = colors.red(`(couldn't inspect)`); } try { inspectNewValue = inspect(newValue); - } catch (error) { + } catch { inspectNewValue = colors.red(`(couldn't inspect)`); } diff --git a/src/data/checks.js b/src/data/checks.js index 52024144..4786f16b 100644 --- a/src/data/checks.js +++ b/src/data/checks.js @@ -4,13 +4,14 @@ import {inspect as nodeInspect} from 'node:util'; import {colors, ENABLE_COLOR} from '#cli'; import CacheableObject from '#cacheable-object'; -import {replacerSpec, parseInput} from '#replacer'; +import {replacerSpec, parseContentNodes} from '#replacer'; import {compareArrays, cut, cutStart, empty, getNestedProp, iterateMultiline} from '#sugar'; import Thing from '#thing'; import thingConstructors from '#things'; import { + annotateError, annotateErrorWithIndex, conditionallySuppressError, decorateErrorWithIndex, @@ -165,6 +166,69 @@ function getFieldPropertyMessage(yamlDocumentSpec, property) { return fieldPropertyMessage; } +function decoAnnotateFindErrors(findFn) { + function annotateMultipleNameMatchesIncludingUnfortunatelyUnsecondary(error) { + const matches = error[Symbol.for('hsmusic.find.multipleNameMatches')]; + if (!matches) return; + + const notSoSecondary = + matches + .map(match => match.thing ?? match) + .filter(match => + match.isTrack && + match.isMainRelease && + CacheableObject.getUpdateValue(match, 'mainRelease')); + + if (empty(notSoSecondary)) return; + + let {message} = error; + message += (message.includes('\n') ? '\n\n' : '\n'); + message += colors.bright(colors.yellow('<!>')) + ' '; + message += colors.yellow(`Some of these tracks are meant to be secondary releases,`) + '\n'; + message += ' '.repeat(4); + message += colors.yellow(`but another error is keeping that from processing correctly!`) + '\n'; + message += ' '.repeat(4); + message += colors.yellow(`Probably look for an error to do with "Main Release", first.`); + Object.assign(error, {message}); + } + + return (...args) => { + try { + return findFn(...args); + } catch (caughtError) { + throw annotateError(caughtError, ...[ + annotateMultipleNameMatchesIncludingUnfortunatelyUnsecondary, + ]); + } + }; +} + +function decoSuppressFindErrors(findFn, {property}) { + void property; + + return conditionallySuppressError(_error => { + // We're not suppressing any errors at the moment. + // An old suppression is kept below for reference. + + /* + if (property === 'sampledTracks') { + // Suppress "didn't match anything" errors in particular, just for samples. + // In hsmusic-data we have a lot of "stub" sample data which don't have + // corresponding tracks yet, so it won't be useful to report such reference + // errors until we take the time to address that. But other errors, like + // malformed reference strings or miscapitalized existing tracks, should + // still be reported, as samples of existing tracks *do* display on the + // website! + if (error.message.includes(`Didn't match anything`)) { + return true; + } + } + */ + + return false; + }, findFn); +} + // Warn about references across data which don't match anything. This involves // using the find() functions on all references, setting it to 'error' mode, and // collecting everything in a structured logged (which gets logged if there are @@ -185,16 +249,20 @@ export function filterReferenceErrors(wikiData, { artTags: '_artTag', referencedArtworks: '_artwork', commentary: '_content', - creditSources: '_content', + creditingSources: '_content', }], ['artTagData', { directDescendantArtTags: 'artTag', }], + ['artworkData', { + referencedArtworks: '_artwork', + }], + ['flashData', { commentary: '_content', - creditSources: '_content', + creditingSources: '_content', }], ['groupCategoryData', { @@ -221,21 +289,23 @@ export function filterReferenceErrors(wikiData, { flashes: 'flash', }], - ['groupData', { - serieses: '_serieses', + ['seriesData', { + albums: 'album', }], ['trackData', { artistContribs: '_contrib', contributorContribs: '_contrib', coverArtistContribs: '_contrib', + previousProductionTracks: '_trackMainReleasesOnly', referencedTracks: '_trackMainReleasesOnly', sampledTracks: '_trackMainReleasesOnly', artTags: '_artTag', referencedArtworks: '_artwork', - mainReleaseTrack: '_trackMainReleasesOnly', + mainRelease: '_mainRelease', commentary: '_content', - creditSources: '_content', + creditingSources: '_content', + referencingSources: '_content', lyrics: '_content', }], @@ -290,15 +360,6 @@ export function filterReferenceErrors(wikiData, { // need writing, humm...) writeProperty = false; break; - - case '_serieses': - if (value) { - // Doesn't report on which series has the error, but... - value = value.flatMap(series => series.albums); - } - - writeProperty = false; - break; } if (value === undefined) { @@ -350,8 +411,94 @@ export function filterReferenceErrors(wikiData, { }; break; - case '_serieses': - findFn = boundFind.album; + case '_mainRelease': + findFn = ref => { + // Mocking what's going on in `withMainRelease`. + + if (ref === 'same name single') { + // Accessing the current thing here. + try { + return boundFind.albumSinglesOnly(thing.name, { + fuzz: { + capitalization: true, + kebab: true, + }, + }); + } catch (caughtError) { + throw new Error( + `Didn't match a single with the same name`, + {cause: caughtError}); + } + } + + let track, trackError; + let album, albumError; + + try { + track = boundFind.trackMainReleasesOnly(ref); + } catch (caughtError) { + trackError = new Error( + `Didn't match a track`, {cause: caughtError}); + } + + try { + album = boundFind.album(ref); + } catch (caughtError) { + albumError = new Error( + `Didn't match an album`, {cause: caughtError}); + } + + if (track && album) { + if (album.tracks.includes(track)) { + return track; + } else { + throw new Error( + `Unrelated album and track matched for reference "${ref}". Please resolve:\n` + + `- ${inspect(track)}\n` + + `- ${inspect(album)}\n` + + `Returning null for this reference.`); + } + } + + if (track) { + return track; + } + + if (album) { + // At this point verification depends on the thing itself, + // which is currently in lexical scope, but if this code + // gets refactored, there might be trouble here... + + if (thing.mainReleaseTrack === null) { + if (album === thing.album) { + throw new Error( + `Matched album for reference "${ref}":\n` + + `- ` + inspect(album) + `\n` + + `...but this is the album that includes this secondary release, itself.\n` + + `Please resolve by pointing to aonther album here, or by removing this\n` + + `Main Release field, if this track is meant to be the main release.`); + } else { + throw new Error( + `Matched album for reference "${ref}":\n` + + `- ` + inspect(album) + `\n` + + `...but none of its tracks automatically match this secondary release.\n` + + `Please resolve by specifying the track here, instead of the album.`); + } + } else { + return album; + } + } + + const aggregateCause = + new AggregateError([albumError, trackError]); + + aggregateCause[Symbol.for('hsmusic.aggregate.translucent')] = true; + + throw new Error(`Trouble matching "${ref}"`, { + cause: aggregateCause, + }); + } + break; case '_trackArtwork': @@ -360,9 +507,16 @@ export function filterReferenceErrors(wikiData, { case '_trackMainReleasesOnly': findFn = trackRef => { - const track = boundFind.track(trackRef); - const mainRef = track && CacheableObject.getUpdateValue(track, 'mainReleaseTrack'); + let track = boundFind.trackMainReleasesOnly(trackRef, {mode: 'quiet'}); + if (track) { + return track; + } + + // Will error normally, if this can't unambiguously resolve + // or doesn't match any track. + track = boundFind.track(trackRef); + const mainRef = CacheableObject.getUpdateValue(track, 'mainRelease'); if (mainRef) { // It's possible for the main release to not actually exist, in this case. // It should still be reported since the 'Main Release' field was present. @@ -393,27 +547,8 @@ export function filterReferenceErrors(wikiData, { break; } - const suppress = fn => conditionallySuppressError(error => { - // We're not suppressing any errors at the moment. - // An old suppression is kept below for reference. - - /* - if (property === 'sampledTracks') { - // Suppress "didn't match anything" errors in particular, just for samples. - // In hsmusic-data we have a lot of "stub" sample data which don't have - // corresponding tracks yet, so it won't be useful to report such reference - // errors until we take the time to address that. But other errors, like - // malformed reference strings or miscapitalized existing tracks, should - // still be reported, as samples of existing tracks *do* display on the - // website! - if (error.message.includes(`Didn't match anything`)) { - return true; - } - } - */ - - return false; - }, fn); + findFn = decoSuppressFindErrors(findFn, {property}); + findFn = decoAnnotateFindErrors(findFn); const fieldPropertyMessage = getFieldPropertyMessage( @@ -469,10 +604,10 @@ export function filterReferenceErrors(wikiData, { value, {message: errorMessage}, decorateErrorWithIndex(refs => (refs.length === 1 - ? suppress(findFn)(refs[0]) + ? findFn(refs[0]) : filterAggregate( refs, {message: `Errors in entry's artist references`}, - decorateErrorWithIndex(suppress(findFn))) + decorateErrorWithIndex(findFn)) .aggregate .close()))); @@ -484,19 +619,18 @@ export function filterReferenceErrors(wikiData, { if (Array.isArray(value)) { newPropertyValue = filter( value, {message: errorMessage}, - decorateErrorWithIndex(suppress(findFn))); + decorateErrorWithIndex(findFn)); break determineNewPropertyValue; } - nest({message: errorMessage}, - suppress(({call}) => { - try { - call(findFn, value); - } catch (error) { - newPropertyValue = null; - throw error; - } - })); + nest({message: errorMessage}, ({call}) => { + try { + call(findFn, value); + } catch (error) { + newPropertyValue = null; + throw error; + } + }); } if (writeProperty) { @@ -520,7 +654,11 @@ export class ContentNodeError extends Error { message, }) { const headingLine = - `(${where}) ${message}`; + (message.includes('\n\n') + ? `(${where})\n\n` + message + '\n' + : message.includes('\n') + ? `(${where})\n` + message + : `(${where}) ${message}`); const textUpToNode = containingLine.slice(0, columnNumber); @@ -565,15 +703,20 @@ export function reportContentTextErrors(wikiData, { description: 'description', }; + const artworkShape = { + source: 'artwork source', + originDetails: 'artwork origin details', + }; + const commentaryShape = { body: 'commentary body', - artistDisplayText: 'commentary artist display text', + artistText: 'commentary artist text', annotation: 'commentary annotation', }; const lyricsShape = { body: 'lyrics body', - artistDisplayText: 'lyrics artist display text', + artistText: 'lyrics artist text', annotation: 'lyrics annotation', }; @@ -581,6 +724,8 @@ export function reportContentTextErrors(wikiData, { ['albumData', { additionalFiles: additionalFileShape, commentary: commentaryShape, + creditingSources: commentaryShape, + coverArtworks: artworkShape, }], ['artTagData', { @@ -593,6 +738,8 @@ export function reportContentTextErrors(wikiData, { ['flashData', { commentary: commentaryShape, + creditingSources: commentaryShape, + coverArtwork: artworkShape, }], ['flashActData', { @@ -622,10 +769,12 @@ export function reportContentTextErrors(wikiData, { ['trackData', { additionalFiles: additionalFileShape, commentary: commentaryShape, - creditSources: commentaryShape, + creditingSources: commentaryShape, + referencingSources: commentaryShape, lyrics: lyricsShape, midiProjectFiles: additionalFileShape, sheetMusicFiles: additionalFileShape, + trackArtworks: artworkShape, }], ['wikiInfo', { @@ -634,11 +783,19 @@ export function reportContentTextErrors(wikiData, { }], ]; - const boundFind = bindFind(wikiData, {mode: 'error'}); + const boundFind = + bindFind(wikiData, { + mode: 'error', + fuzz: { + capitalization: true, + kebab: true, + }, + }); + const findArtistOrAlias = bindFindArtistOrAlias(boundFind); function* processContent(input) { - const nodes = parseInput(input); + const nodes = parseContentNodes(input); for (const node of nodes) { const index = node.i; @@ -675,6 +832,9 @@ export function reportContentTextErrors(wikiData, { break; } + findFn = decoSuppressFindErrors(findFn, {property: null}); + findFn = decoAnnotateFindErrors(findFn); + const findRef = (replacerKeyImplied ? replacerValue @@ -695,7 +855,7 @@ export function reportContentTextErrors(wikiData, { } else if (node.type === 'external-link') { try { new URL(node.data.href); - } catch (error) { + } catch { yield { index, length, message: @@ -766,6 +926,31 @@ export function reportContentTextErrors(wikiData, { const topMessage = `Content text errors` + fieldPropertyMessage; + const checkShapeEntries = (entry, callProcessContentOpts) => { + for (const [key, annotation] of Object.entries(shape)) { + const value = entry[key]; + + // TODO: This should be an undefined/null check, like above, + // but it's not, because sometimes the stuff we're checking + // here isn't actually coded as a Thing - so the properties + // might really be undefined instead of null. Terrifying and + // awful. And most of all, citation needed. + if (!value) continue; + + callProcessContent({ + ...callProcessContentOpts, + + // TODO: `nest` isn't provided by `callProcessContentOpts` + //`but `push` is - this is to match the old code, but + // what's the deal here? + nest, + + value, + message: `Error in ${colors.green(annotation)}`, + }); + } + }; + if (shape === '_content') { callProcessContent({ nest, @@ -773,26 +958,18 @@ export function reportContentTextErrors(wikiData, { value, message: topMessage, }); - } else { + } else if (Array.isArray(value)) { nest({message: topMessage}, ({push}) => { for (const [index, entry] of value.entries()) { - for (const [key, annotation] of Object.entries(shape)) { - const value = entry[key]; - - // TODO: Should this check undefined/null similar to above? - if (!value) continue; - - callProcessContent({ - nest, - push, - value, - message: `Error in ${colors.green(annotation)}`, - annotateError: error => - annotateErrorWithIndex(error, index), - }); - } + checkShapeEntries(entry, { + push, + annotateError: error => + annotateErrorWithIndex(error, index), + }); } }); + } else { + checkShapeEntries(value, {push}); } } }); diff --git a/src/data/composite.js b/src/data/composite.js index f31c4069..e5873cf5 100644 --- a/src/data/composite.js +++ b/src/data/composite.js @@ -1416,7 +1416,7 @@ export function compositeFrom(description) { export function displayCompositeCacheAnalysis() { const showTimes = (cache, key) => { - const times = cache.times[key].slice().sort(); + const times = cache.times[key].toSorted(); const all = times; const worst10pc = times.slice(-times.length / 10); diff --git a/src/data/composite/data/index.js b/src/data/composite/data/index.js index 46a3dc81..05b59445 100644 --- a/src/data/composite/data/index.js +++ b/src/data/composite/data/index.js @@ -20,6 +20,7 @@ export {default as withMappedList} from './withMappedList.js'; export {default as withSortedList} from './withSortedList.js'; export {default as withStretchedList} from './withStretchedList.js'; +export {default as withLengthOfList} from './withLengthOfList.js'; export {default as withPropertyFromList} from './withPropertyFromList.js'; export {default as withPropertiesFromList} from './withPropertiesFromList.js'; diff --git a/src/data/composite/data/withLengthOfList.js b/src/data/composite/data/withLengthOfList.js new file mode 100644 index 00000000..e67aa887 --- /dev/null +++ b/src/data/composite/data/withLengthOfList.js @@ -0,0 +1,54 @@ +import {input, templateCompositeFrom} from '#composite'; + +function getOutputName({ + [input.staticDependency('list')]: list, +}) { + if (list && list.startsWith('#')) { + return `${list}.length`; + } else if (list) { + return `#${list}.length`; + } else { + return '#length'; + } +} + +export default templateCompositeFrom({ + annotation: `withMappedList`, + + inputs: { + list: input({type: 'array'}), + }, + + outputs: inputs => [getOutputName(inputs)], + + steps: () => [ + { + dependencies: [input.staticDependency('list')], + compute: (continuation, inputs) => + continuation({'#output': getOutputName(inputs)}), + }, + + { + dependencies: [input('list')], + compute: (continuation, { + [input('list')]: list, + }) => continuation({ + ['#value']: + (list === null + ? null + : list.length), + }), + }, + + { + dependencies: ['#output', '#value'], + + compute: (continuation, { + ['#output']: output, + ['#value']: value, + }) => continuation({ + [output]: value, + }), + }, + ], +}); diff --git a/src/data/composite/things/album/index.js b/src/data/composite/things/album/index.js index dfc6864f..de1d37c3 100644 --- a/src/data/composite/things/album/index.js +++ b/src/data/composite/things/album/index.js @@ -1,2 +1,2 @@ -export {default as withHasCoverArt} from './withHasCoverArt.js'; +export {default as withCoverArtDate} from './withCoverArtDate.js'; export {default as withTracks} from './withTracks.js'; diff --git a/src/data/composite/wiki-data/withCoverArtDate.js b/src/data/composite/things/album/withCoverArtDate.js index a114d5ff..978f566a 100644 --- a/src/data/composite/wiki-data/withCoverArtDate.js +++ b/src/data/composite/things/album/withCoverArtDate.js @@ -2,8 +2,7 @@ import {input, templateCompositeFrom} from '#composite'; import {isDate} from '#validators'; import {raiseOutputWithoutDependency} from '#composite/control-flow'; - -import withResolvedContribs from './withResolvedContribs.js'; +import {withHasArtwork} from '#composite/wiki-data'; export default templateCompositeFrom({ annotation: `withCoverArtDate`, @@ -19,14 +18,14 @@ export default templateCompositeFrom({ outputs: ['#coverArtDate'], steps: () => [ - withResolvedContribs({ - from: 'coverArtistContribs', - date: input.value(null), + withHasArtwork({ + contribs: 'coverArtistContribs', + artworks: 'coverArtworks', }), raiseOutputWithoutDependency({ - dependency: '#resolvedContribs', - mode: input.value('empty'), + dependency: '#hasArtwork', + mode: input.value('falsy'), output: input.value({'#coverArtDate': null}), }), diff --git a/src/data/composite/things/artwork/index.js b/src/data/composite/things/artwork/index.js index 3693c10f..b5e5e167 100644 --- a/src/data/composite/things/artwork/index.js +++ b/src/data/composite/things/artwork/index.js @@ -1,5 +1,7 @@ +export {default as withArtTags} from './withArtTags.js'; export {default as withAttachedArtwork} from './withAttachedArtwork.js'; export {default as withContainingArtworkList} from './withContainingArtworkList.js'; +export {default as withContentWarningArtTags} from './withContentWarningArtTags.js'; export {default as withContribsFromAttachedArtwork} from './withContribsFromAttachedArtwork.js'; export {default as withDate} from './withDate.js'; export {default as withPropertyFromAttachedArtwork} from './withPropertyFromAttachedArtwork.js'; diff --git a/src/data/composite/things/artwork/withArtTags.js b/src/data/composite/things/artwork/withArtTags.js new file mode 100644 index 00000000..1fed3c31 --- /dev/null +++ b/src/data/composite/things/artwork/withArtTags.js @@ -0,0 +1,99 @@ +import {input, templateCompositeFrom} from '#composite'; + +import {raiseOutputWithoutDependency, withResultOfAvailabilityCheck} + from '#composite/control-flow'; +import {withPropertyFromObject} from '#composite/data'; +import {withResolvedReferenceList} from '#composite/wiki-data'; +import {soupyFind} from '#composite/wiki-properties'; + +import withPropertyFromAttachedArtwork + from './withPropertyFromAttachedArtwork.js'; + +export default templateCompositeFrom({ + annotation: `withArtTags`, + + inputs: { + from: input({ + type: 'array', + acceptsNull: true, + defaultDependency: 'artTags', + }), + }, + + outputs: ['#artTags'], + + steps: () => [ + withResolvedReferenceList({ + list: input('from'), + find: soupyFind.input('artTag'), + }), + + withResultOfAvailabilityCheck({ + from: '#resolvedReferenceList', + mode: input.value('empty'), + }), + + { + dependencies: ['#availability', '#resolvedReferenceList'], + compute: (continuation, { + ['#availability']: availability, + ['#resolvedReferenceList']: resolvedReferenceList, + }) => + (availability + ? continuation.raiseOutput({ + '#artTags': resolvedReferenceList, + }) + : continuation()), + }, + + withPropertyFromAttachedArtwork({ + property: input.value('artTags'), + }), + + withResultOfAvailabilityCheck({ + from: '#attachedArtwork.artTags', + mode: input.value('empty'), + }), + + { + dependencies: ['#availability', '#attachedArtwork.artTags'], + compute: (continuation, { + ['#availability']: availability, + ['#attachedArtwork.artTags']: attachedArtworkArtTags, + }) => + (availability + ? continuation.raiseOutput({ + '#artTags': attachedArtworkArtTags, + }) + : continuation()), + }, + + raiseOutputWithoutDependency({ + dependency: 'artTagsFromThingProperty', + output: input.value({'#artTags': []}), + }), + + withPropertyFromObject({ + object: 'thing', + property: 'artTagsFromThingProperty', + }).outputs({ + ['#value']: '#thing.artTags', + }), + + withResultOfAvailabilityCheck({ + from: '#thing.artTags', + mode: input.value('empty'), + }), + + { + dependencies: ['#availability', '#thing.artTags'], + compute: (continuation, { + ['#availability']: availability, + ['#thing.artTags']: thingArtTags, + }) => + (availability + ? continuation({'#artTags': thingArtTags}) + : continuation({'#artTags': []})), + }, + ], +}); diff --git a/src/data/composite/things/artwork/withContentWarningArtTags.js b/src/data/composite/things/artwork/withContentWarningArtTags.js new file mode 100644 index 00000000..4c07e837 --- /dev/null +++ b/src/data/composite/things/artwork/withContentWarningArtTags.js @@ -0,0 +1,27 @@ +import {input, templateCompositeFrom} from '#composite'; + +import {withFilteredList, withPropertyFromList} from '#composite/data'; + +import withArtTags from './withArtTags.js'; + +export default templateCompositeFrom({ + annotation: `withContentWarningArtTags`, + + outputs: ['#contentWarningArtTags'], + + steps: () => [ + withArtTags(), + + withPropertyFromList({ + list: '#artTags', + property: input.value('isContentWarning'), + }), + + withFilteredList({ + list: '#artTags', + filter: '#artTags.isContentWarning', + }).outputs({ + '#filteredList': '#contentWarningArtTags', + }), + ], +}); diff --git a/src/data/composite/things/artwork/withContribsFromAttachedArtwork.js b/src/data/composite/things/artwork/withContribsFromAttachedArtwork.js index 36abb3fe..e9425c95 100644 --- a/src/data/composite/things/artwork/withContribsFromAttachedArtwork.js +++ b/src/data/composite/things/artwork/withContribsFromAttachedArtwork.js @@ -1,7 +1,6 @@ import {input, templateCompositeFrom} from '#composite'; import {raiseOutputWithoutDependency} from '#composite/control-flow'; -import {withPropertyFromObject} from '#composite/data'; import {withRecontextualizedContributionList} from '#composite/wiki-data'; import withPropertyFromAttachedArtwork from './withPropertyFromAttachedArtwork.js'; diff --git a/src/data/composite/things/commentary-entry/index.js b/src/data/composite/things/commentary-entry/index.js deleted file mode 100644 index 091bae1a..00000000 --- a/src/data/composite/things/commentary-entry/index.js +++ /dev/null @@ -1 +0,0 @@ -export {default as withWebArchiveDate} from './withWebArchiveDate.js'; diff --git a/src/data/composite/things/content/contentArtists.js b/src/data/composite/things/content/contentArtists.js new file mode 100644 index 00000000..8d5db5a5 --- /dev/null +++ b/src/data/composite/things/content/contentArtists.js @@ -0,0 +1,40 @@ +import {input, templateCompositeFrom} from '#composite'; +import {validateReferenceList} from '#validators'; + +import {exitWithoutDependency, exposeDependency} + from '#composite/control-flow'; +import {withResolvedReferenceList} from '#composite/wiki-data'; +import {soupyFind} from '#composite/wiki-properties'; + +import withExpressedOrImplicitArtistReferences + from './helpers/withExpressedOrImplicitArtistReferences.js'; + +export default templateCompositeFrom({ + annotation: `contentArtists`, + + compose: false, + + update: { + validate: validateReferenceList('artist'), + }, + + steps: () => [ + withExpressedOrImplicitArtistReferences({ + from: input.updateValue(), + }), + + exitWithoutDependency({ + dependency: '#artistReferences', + value: input.value([]), + }), + + withResolvedReferenceList({ + list: '#artistReferences', + find: soupyFind.input('artist'), + }), + + exposeDependency({ + dependency: '#resolvedReferenceList', + }), + ], +}); diff --git a/src/data/composite/things/content/hasAnnotationPart.js b/src/data/composite/things/content/hasAnnotationPart.js new file mode 100644 index 00000000..83d175e3 --- /dev/null +++ b/src/data/composite/things/content/hasAnnotationPart.js @@ -0,0 +1,25 @@ +import {input, templateCompositeFrom} from '#composite'; + +import {exposeDependency} from '#composite/control-flow'; + +import withHasAnnotationPart from './withHasAnnotationPart.js'; + +export default templateCompositeFrom({ + annotation: `hasAnnotationPart`, + + compose: false, + + inputs: { + part: input({type: 'string'}), + }, + + steps: () => [ + withHasAnnotationPart({ + part: input('part'), + }), + + exposeDependency({ + dependency: '#hasAnnotationPart', + }), + ], +}); diff --git a/src/data/composite/things/content/helpers/withExpressedOrImplicitArtistReferences.js b/src/data/composite/things/content/helpers/withExpressedOrImplicitArtistReferences.js new file mode 100644 index 00000000..69da8c75 --- /dev/null +++ b/src/data/composite/things/content/helpers/withExpressedOrImplicitArtistReferences.js @@ -0,0 +1,61 @@ +import {input, templateCompositeFrom} from '#composite'; + +import {raiseOutputWithoutDependency} from '#composite/control-flow'; +import {withFilteredList, withMappedList} from '#composite/data'; +import {withContentNodes} from '#composite/wiki-data'; + +export default templateCompositeFrom({ + annotation: `withExpressedOrImplicitArtistReferences`, + + inputs: { + from: input({type: 'array', acceptsNull: true}), + }, + + outputs: ['#artistReferences'], + + steps: () => [ + { + dependencies: [input('from')], + compute: (continuation, { + [input('from')]: expressedArtistReferences, + }) => + (expressedArtistReferences + ? continuation.raiseOutput({'#artistReferences': expressedArtistReferences}) + : continuation()), + }, + + raiseOutputWithoutDependency({ + dependency: 'artistText', + output: input.value({'#artistReferences': null}), + }), + + withContentNodes({ + from: 'artistText', + }), + + withMappedList({ + list: '#contentNodes', + map: input.value(node => + node.type === 'tag' && + node.data.replacerKey?.data === 'artist'), + }).outputs({ + '#mappedList': '#artistTagFilter', + }), + + withFilteredList({ + list: '#contentNodes', + filter: '#artistTagFilter', + }).outputs({ + '#filteredList': '#artistTags', + }), + + withMappedList({ + list: '#artistTags', + map: input.value(node => + 'artist:' + + node.data.replacerValue[0].data), + }).outputs({ + '#mappedList': '#artistReferences', + }), + ], +}); diff --git a/src/data/composite/things/content/index.js b/src/data/composite/things/content/index.js new file mode 100644 index 00000000..4176337d --- /dev/null +++ b/src/data/composite/things/content/index.js @@ -0,0 +1,7 @@ +export {default as contentArtists} from './contentArtists.js'; +export {default as hasAnnotationPart} from './hasAnnotationPart.js'; +export {default as withAnnotationParts} from './withAnnotationParts.js'; +export {default as withHasAnnotationPart} from './withHasAnnotationPart.js'; +export {default as withSourceText} from './withSourceText.js'; +export {default as withSourceURLs} from './withSourceURLs.js'; +export {default as withWebArchiveDate} from './withWebArchiveDate.js'; diff --git a/src/data/composite/things/content/withAnnotationParts.js b/src/data/composite/things/content/withAnnotationParts.js new file mode 100644 index 00000000..0c5a0294 --- /dev/null +++ b/src/data/composite/things/content/withAnnotationParts.js @@ -0,0 +1,103 @@ +import {input, templateCompositeFrom} from '#composite'; +import {empty, transposeArrays} from '#sugar'; +import {is} from '#validators'; + +import {raiseOutputWithoutDependency} from '#composite/control-flow'; +import {withPropertyFromList} from '#composite/data'; +import {splitContentNodesAround, withContentNodes} from '#composite/wiki-data'; + +export default templateCompositeFrom({ + annotation: `withAnnotationParts`, + + inputs: { + mode: input({ + validate: is('strings', 'nodes'), + }), + }, + + outputs: ['#annotationParts'], + + steps: () => [ + raiseOutputWithoutDependency({ + dependency: 'annotation', + output: input.value({'#annotationParts': []}), + }), + + withContentNodes({ + from: 'annotation', + }), + + splitContentNodesAround({ + nodes: '#contentNodes', + around: input.value(/, */g), + }), + + { + dependencies: ['#contentNodeLists'], + compute: (continuation, { + ['#contentNodeLists']: nodeLists, + }) => continuation({ + ['#contentNodeLists']: + nodeLists.filter(list => !empty(list)), + }), + }, + + { + dependencies: ['#contentNodeLists', input('mode')], + compute: (continuation, { + ['#contentNodeLists']: nodeLists, + [input('mode')]: mode, + }) => + (mode === 'nodes' + ? continuation.raiseOutput({'#annotationParts': nodeLists}) + : continuation()), + }, + + { + dependencies: ['#contentNodeLists'], + + compute: (continuation, { + ['#contentNodeLists']: nodeLists, + }) => continuation({ + ['#firstNodes']: + nodeLists.map(list => list.at(0)), + + ['#lastNodes']: + nodeLists.map(list => list.at(-1)), + }), + }, + + withPropertyFromList({ + list: '#firstNodes', + property: input.value('i'), + }).outputs({ + '#firstNodes.i': '#startIndices', + }), + + withPropertyFromList({ + list: '#lastNodes', + property: input.value('iEnd'), + }).outputs({ + '#lastNodes.iEnd': '#endIndices', + }), + + { + dependencies: [ + 'annotation', + '#startIndices', + '#endIndices', + ], + + compute: (continuation, { + ['annotation']: annotation, + ['#startIndices']: startIndices, + ['#endIndices']: endIndices, + }) => continuation({ + ['#annotationParts']: + transposeArrays([startIndices, endIndices]) + .map(([start, end]) => + annotation.slice(start, end)), + }), + }, + ], +}); diff --git a/src/data/composite/things/content/withHasAnnotationPart.js b/src/data/composite/things/content/withHasAnnotationPart.js new file mode 100644 index 00000000..4af554f3 --- /dev/null +++ b/src/data/composite/things/content/withHasAnnotationPart.js @@ -0,0 +1,43 @@ +import {input, templateCompositeFrom} from '#composite'; + +import {raiseOutputWithoutDependency} from '#composite/control-flow'; + +import withAnnotationParts from './withAnnotationParts.js'; + +export default templateCompositeFrom({ + annotation: `withHasAnnotationPart`, + + inputs: { + part: input({type: 'string'}), + }, + + outputs: ['#hasAnnotationPart'], + + steps: () => [ + withAnnotationParts({ + mode: input.value('strings'), + }), + + raiseOutputWithoutDependency({ + dependency: '#annotationParts', + output: input.value({'#hasAnnotationPart': false}), + }), + + { + dependencies: [ + input('part'), + '#annotationParts', + ], + + compute: (continuation, { + [input('part')]: search, + ['#annotationParts']: parts, + }) => continuation({ + ['#hasAnnotationPart']: + parts.some(part => + part.toLowerCase() === + search.toLowerCase()), + }), + }, + ], +}); diff --git a/src/data/composite/things/content/withSourceText.js b/src/data/composite/things/content/withSourceText.js new file mode 100644 index 00000000..292306b7 --- /dev/null +++ b/src/data/composite/things/content/withSourceText.js @@ -0,0 +1,53 @@ +import {input, templateCompositeFrom} from '#composite'; + +import {raiseOutputWithoutDependency} from '#composite/control-flow'; + +import withAnnotationParts from './withAnnotationParts.js'; + +export default templateCompositeFrom({ + annotation: `withSourceText`, + + outputs: ['#sourceText'], + + steps: () => [ + withAnnotationParts({ + mode: input.value('nodes'), + }), + + raiseOutputWithoutDependency({ + dependency: '#annotationParts', + output: input.value({'#sourceText': null}), + }), + + { + dependencies: ['#annotationParts'], + compute: (continuation, { + ['#annotationParts']: annotationParts, + }) => continuation({ + ['#firstPartWithExternalLink']: + annotationParts + .find(nodes => nodes + .some(node => node.type === 'external-link')) ?? + null, + }), + }, + + raiseOutputWithoutDependency({ + dependency: '#firstPartWithExternalLink', + output: input.value({'#sourceText': null}), + }), + + { + dependencies: ['annotation', '#firstPartWithExternalLink'], + compute: (continuation, { + ['annotation']: annotation, + ['#firstPartWithExternalLink']: nodes, + }) => continuation({ + ['#sourceText']: + annotation.slice( + nodes.at(0).i, + nodes.at(-1).iEnd), + }), + }, + ], +}); diff --git a/src/data/composite/things/content/withSourceURLs.js b/src/data/composite/things/content/withSourceURLs.js new file mode 100644 index 00000000..f85ff9ea --- /dev/null +++ b/src/data/composite/things/content/withSourceURLs.js @@ -0,0 +1,62 @@ +import {input, templateCompositeFrom} from '#composite'; + +import {raiseOutputWithoutDependency} from '#composite/control-flow'; +import {withFilteredList, withMappedList} from '#composite/data'; + +import withAnnotationParts from './withAnnotationParts.js'; + +export default templateCompositeFrom({ + annotation: `withSourceURLs`, + + outputs: ['#sourceURLs'], + + steps: () => [ + withAnnotationParts({ + mode: input.value('nodes'), + }), + + raiseOutputWithoutDependency({ + dependency: '#annotationParts', + output: input.value({'#sourceURLs': []}), + }), + + { + dependencies: ['#annotationParts'], + compute: (continuation, { + ['#annotationParts']: annotationParts, + }) => continuation({ + ['#firstPartWithExternalLink']: + annotationParts + .find(nodes => nodes + .some(node => node.type === 'external-link')) ?? + null, + }), + }, + + raiseOutputWithoutDependency({ + dependency: '#firstPartWithExternalLink', + output: input.value({'#sourceURLs': []}), + }), + + withMappedList({ + list: '#firstPartWithExternalLink', + map: input.value(node => node.type === 'external-link'), + }).outputs({ + '#mappedList': '#externalLinkFilter', + }), + + withFilteredList({ + list: '#firstPartWithExternalLink', + filter: '#externalLinkFilter', + }).outputs({ + '#filteredList': '#externalLinks', + }), + + withMappedList({ + list: '#externalLinks', + map: input.value(node => node.data.href), + }).outputs({ + '#mappedList': '#sourceURLs', + }), + ], +}); diff --git a/src/data/composite/things/commentary-entry/withWebArchiveDate.js b/src/data/composite/things/content/withWebArchiveDate.js index 3aaa4f64..3aaa4f64 100644 --- a/src/data/composite/things/commentary-entry/withWebArchiveDate.js +++ b/src/data/composite/things/content/withWebArchiveDate.js diff --git a/src/data/composite/things/contribution/index.js b/src/data/composite/things/contribution/index.js index 9b22be2e..31d86b8b 100644 --- a/src/data/composite/things/contribution/index.js +++ b/src/data/composite/things/contribution/index.js @@ -1,6 +1,4 @@ export {default as inheritFromContributionPresets} from './inheritFromContributionPresets.js'; -export {default as thingPropertyMatches} from './thingPropertyMatches.js'; -export {default as thingReferenceTypeMatches} from './thingReferenceTypeMatches.js'; export {default as withContainingReverseContributionList} from './withContainingReverseContributionList.js'; export {default as withContributionArtist} from './withContributionArtist.js'; export {default as withContributionContext} from './withContributionContext.js'; diff --git a/src/data/composite/things/contribution/thingPropertyMatches.js b/src/data/composite/things/contribution/thingPropertyMatches.js deleted file mode 100644 index 1e9019b8..00000000 --- a/src/data/composite/things/contribution/thingPropertyMatches.js +++ /dev/null @@ -1,46 +0,0 @@ -import {input, templateCompositeFrom} from '#composite'; - -import {exitWithoutDependency} from '#composite/control-flow'; -import {withPropertyFromObject} from '#composite/data'; - -export default templateCompositeFrom({ - annotation: `thingPropertyMatches`, - - compose: false, - - inputs: { - value: input({type: 'string'}), - }, - - steps: () => [ - { - dependencies: ['thing', 'thingProperty'], - - compute: (continuation, {thing, thingProperty}) => - continuation({ - ['#thingProperty']: - (thing.constructor[Symbol.for('Thing.referenceType')] === 'artwork' - ? thing.artistContribsFromThingProperty - : thingProperty), - }), - }, - - exitWithoutDependency({ - dependency: '#thingProperty', - value: input.value(false), - }), - - { - dependencies: [ - '#thingProperty', - input('value'), - ], - - compute: ({ - ['#thingProperty']: thingProperty, - [input('value')]: value, - }) => - thingProperty === value, - }, - ], -}); diff --git a/src/data/composite/things/contribution/thingReferenceTypeMatches.js b/src/data/composite/things/contribution/thingReferenceTypeMatches.js deleted file mode 100644 index 4042e78f..00000000 --- a/src/data/composite/things/contribution/thingReferenceTypeMatches.js +++ /dev/null @@ -1,66 +0,0 @@ -import {input, templateCompositeFrom} from '#composite'; - -import {exitWithoutDependency} from '#composite/control-flow'; -import {withPropertyFromObject} from '#composite/data'; - -export default templateCompositeFrom({ - annotation: `thingReferenceTypeMatches`, - - compose: false, - - inputs: { - value: input({type: 'string'}), - }, - - steps: () => [ - exitWithoutDependency({ - dependency: 'thing', - value: input.value(false), - }), - - withPropertyFromObject({ - object: 'thing', - property: input.value('constructor'), - }), - - { - dependencies: [ - '#thing.constructor', - input('value'), - ], - - compute: (continuation, { - ['#thing.constructor']: constructor, - [input('value')]: value, - }) => - (constructor[Symbol.for('Thing.referenceType')] === value - ? continuation.exit(true) - : constructor[Symbol.for('Thing.referenceType')] === 'artwork' - ? continuation() - : continuation.exit(false)), - }, - - withPropertyFromObject({ - object: 'thing', - property: input.value('thing'), - }), - - withPropertyFromObject({ - object: '#thing.thing', - property: input.value('constructor'), - }), - - { - dependencies: [ - '#thing.thing.constructor', - input('value'), - ], - - compute: ({ - ['#thing.thing.constructor']: constructor, - [input('value')]: value, - }) => - constructor[Symbol.for('Thing.referenceType')] === value, - }, - ], -}); diff --git a/src/data/composite/things/language/index.js b/src/data/composite/things/language/index.js new file mode 100644 index 00000000..f22cdaf6 --- /dev/null +++ b/src/data/composite/things/language/index.js @@ -0,0 +1 @@ +export {default as withStrings} from './withStrings.js'; diff --git a/src/data/composite/things/language/withStrings.js b/src/data/composite/things/language/withStrings.js new file mode 100644 index 00000000..3b8d46b3 --- /dev/null +++ b/src/data/composite/things/language/withStrings.js @@ -0,0 +1,111 @@ +import {logWarn} from '#cli'; +import {input, templateCompositeFrom} from '#composite'; +import {empty, withEntries} from '#sugar'; +import {languageOptionRegex} from '#wiki-data'; + +import {withResultOfAvailabilityCheck} from '#composite/control-flow'; + +export default templateCompositeFrom({ + annotation: `withStrings`, + + inputs: { + from: input({defaultDependency: 'strings'}), + }, + + outputs: ['#strings'], + + steps: () => [ + withResultOfAvailabilityCheck({ + from: input('from'), + }).outputs({ + '#availability': '#stringsAvailability', + }), + + withResultOfAvailabilityCheck({ + from: 'inheritedStrings', + }).outputs({ + '#availability': '#inheritedStringsAvailability', + }), + + { + dependencies: [ + '#stringsAvailability', + '#inheritedStringsAvailability', + ], + + compute: (continuation, { + ['#stringsAvailability']: stringsAvailability, + ['#inheritedStringsAvailability']: inheritedStringsAvailability, + }) => + (stringsAvailability || inheritedStringsAvailability + ? continuation() + : continuation.raiseOutput({'#strings': null})), + }, + + { + dependencies: [input('from'), '#inheritedStringsAvailability'], + compute: (continuation, { + [input('from')]: strings, + ['#inheritedStringsAvailability']: inheritedStringsAvailability, + }) => + (inheritedStringsAvailability + ? continuation() + : continuation.raiseOutput({'#strings': strings})), + }, + + { + dependencies: ['inheritedStrings', '#stringsAvailability'], + compute: (continuation, { + ['inheritedStrings']: inheritedStrings, + ['#stringsAvailability']: stringsAvailability, + }) => + (stringsAvailability + ? continuation() + : continuation.raiseOutput({'#strings': inheritedStrings})), + }, + + { + dependencies: [input('from'), 'inheritedStrings', 'code'], + compute(continuation, { + [input('from')]: strings, + ['inheritedStrings']: inheritedStrings, + ['code']: code, + }) { + const validStrings = { + ...inheritedStrings, + ...strings, + }; + + const optionsFromTemplate = template => + Array.from(template.matchAll(languageOptionRegex)) + .map(({groups}) => groups.name); + + for (const [key, providedTemplate] of Object.entries(strings)) { + const inheritedTemplate = inheritedStrings[key]; + if (!inheritedTemplate) continue; + + const providedOptions = optionsFromTemplate(providedTemplate); + const inheritedOptions = optionsFromTemplate(inheritedTemplate); + + const missingOptionNames = + inheritedOptions.filter(name => !providedOptions.includes(name)); + + const misplacedOptionNames = + providedOptions.filter(name => !inheritedOptions.includes(name)); + + if (!empty(missingOptionNames) || !empty(misplacedOptionNames)) { + logWarn`Not using ${code ?? '(no code)'} string ${key}:`; + if (!empty(missingOptionNames)) + logWarn`- Missing options: ${missingOptionNames.join(', ')}`; + if (!empty(misplacedOptionNames)) + logWarn`- Unexpected options: ${misplacedOptionNames.join(', ')}`; + + validStrings[key] = inheritedStrings[key]; + } + } + + return continuation({'#strings': validStrings}); + }, + }, + ], +}); diff --git a/src/data/composite/things/track-section/withContinueCountingFrom.js b/src/data/composite/things/track-section/withContinueCountingFrom.js index e034b7a5..0ca52b6c 100644 --- a/src/data/composite/things/track-section/withContinueCountingFrom.js +++ b/src/data/composite/things/track-section/withContinueCountingFrom.js @@ -1,4 +1,4 @@ -import {input, templateCompositeFrom} from '#composite'; +import {templateCompositeFrom} from '#composite'; import withStartCountingFrom from './withStartCountingFrom.js'; diff --git a/src/data/composite/things/track/alwaysReferenceByDirectory.js b/src/data/composite/things/track/alwaysReferenceByDirectory.js new file mode 100644 index 00000000..a342d38b --- /dev/null +++ b/src/data/composite/things/track/alwaysReferenceByDirectory.js @@ -0,0 +1,69 @@ +// Controls how find.track works - it'll never be matched by a reference +// just to the track's name, which means you don't have to always reference +// some *other* (much more commonly referenced) track by directory instead +// of more naturally by name. + +import {input, templateCompositeFrom} from '#composite'; +import {isBoolean} from '#validators'; +import {getKebabCase} from '#wiki-data'; + +import {withPropertyFromObject} from '#composite/data'; + +import { + exitWithoutDependency, + exposeDependencyOrContinue, + exposeUpdateValueOrContinue, +} from '#composite/control-flow'; + +import withMainReleaseTrack from './withMainReleaseTrack.js'; +import withPropertyFromAlbum from './withPropertyFromAlbum.js'; + +export default templateCompositeFrom({ + annotation: `alwaysReferenceByDirectory`, + + compose: false, + + steps: () => [ + exposeUpdateValueOrContinue({ + validate: input.value(isBoolean), + }), + + withPropertyFromAlbum({ + property: input.value('alwaysReferenceTracksByDirectory'), + }), + + // Falsy mode means this exposes true if the album's property is true, + // but continues if the property is false (which is also the default). + exposeDependencyOrContinue({ + dependency: '#album.alwaysReferenceTracksByDirectory', + mode: input.value('falsy'), + }), + + exitWithoutDependency({ + dependency: 'mainRelease', + value: input.value(false), + }), + + withMainReleaseTrack(), + + exitWithoutDependency({ + dependency: '#mainReleaseTrack', + value: input.value(false), + }), + + withPropertyFromObject({ + object: '#mainReleaseTrack', + property: input.value('name'), + }), + + { + dependencies: ['name', '#mainReleaseTrack.name'], + compute: ({ + ['name']: name, + ['#mainReleaseTrack.name']: mainReleaseName, + }) => + getKebabCase(name) === + getKebabCase(mainReleaseName), + }, + ], +}); diff --git a/src/data/composite/things/track/index.js b/src/data/composite/things/track/index.js index e789e736..1c203cd9 100644 --- a/src/data/composite/things/track/index.js +++ b/src/data/composite/things/track/index.js @@ -1,14 +1,15 @@ +export {default as alwaysReferenceByDirectory} from './alwaysReferenceByDirectory.js'; export {default as exitWithoutUniqueCoverArt} from './exitWithoutUniqueCoverArt.js'; export {default as inheritContributionListFromMainRelease} from './inheritContributionListFromMainRelease.js'; export {default as inheritFromMainRelease} from './inheritFromMainRelease.js'; export {default as withAllReleases} from './withAllReleases.js'; -export {default as withAlwaysReferenceByDirectory} from './withAlwaysReferenceByDirectory.js'; export {default as withContainingTrackSection} from './withContainingTrackSection.js'; export {default as withCoverArtistContribs} from './withCoverArtistContribs.js'; export {default as withDate} from './withDate.js'; export {default as withDirectorySuffix} from './withDirectorySuffix.js'; export {default as withHasUniqueCoverArt} from './withHasUniqueCoverArt.js'; export {default as withMainRelease} from './withMainRelease.js'; +export {default as withMainReleaseTrack} from './withMainReleaseTrack.js'; export {default as withOtherReleases} from './withOtherReleases.js'; export {default as withPropertyFromAlbum} from './withPropertyFromAlbum.js'; export {default as withPropertyFromMainRelease} from './withPropertyFromMainRelease.js'; diff --git a/src/data/composite/things/track/trackAdditionalNameList.js b/src/data/composite/things/track/trackAdditionalNameList.js deleted file mode 100644 index 65a2263d..00000000 --- a/src/data/composite/things/track/trackAdditionalNameList.js +++ /dev/null @@ -1,38 +0,0 @@ -// Compiles additional names from various sources. - -import {input, templateCompositeFrom} from '#composite'; -import {isAdditionalNameList} from '#validators'; - -import withInferredAdditionalNames from './withInferredAdditionalNames.js'; -import withSharedAdditionalNames from './withSharedAdditionalNames.js'; - -export default templateCompositeFrom({ - annotation: `trackAdditionalNameList`, - - compose: false, - - update: {validate: isAdditionalNameList}, - - steps: () => [ - withInferredAdditionalNames(), - withSharedAdditionalNames(), - - { - dependencies: [ - '#inferredAdditionalNames', - '#sharedAdditionalNames', - input.updateValue(), - ], - - compute: ({ - ['#inferredAdditionalNames']: inferredAdditionalNames, - ['#sharedAdditionalNames']: sharedAdditionalNames, - [input.updateValue()]: providedAdditionalNames, - }) => [ - ...providedAdditionalNames ?? [], - ...sharedAdditionalNames, - ...inferredAdditionalNames, - ], - }, - ], -}); diff --git a/src/data/composite/things/track/withAllReleases.js b/src/data/composite/things/track/withAllReleases.js index b93bf753..bd54384f 100644 --- a/src/data/composite/things/track/withAllReleases.js +++ b/src/data/composite/things/track/withAllReleases.js @@ -8,10 +8,9 @@ import {input, templateCompositeFrom} from '#composite'; import {sortByDate} from '#sort'; -import {exitWithoutDependency} from '#composite/control-flow'; import {withPropertyFromObject} from '#composite/data'; -import withMainRelease from './withMainRelease.js'; +import withMainReleaseTrack from './withMainReleaseTrack.js'; export default templateCompositeFrom({ annotation: `withAllReleases`, @@ -19,7 +18,7 @@ export default templateCompositeFrom({ outputs: ['#allReleases'], steps: () => [ - withMainRelease({ + withMainReleaseTrack({ selfIfMain: input.value(true), notFoundValue: input.value([]), }), @@ -29,18 +28,22 @@ export default templateCompositeFrom({ // `this.secondaryReleases` from within a data composition. // Oooooooooooooooooooooooooooooooooooooooooooooooo withPropertyFromObject({ - object: '#mainRelease', + object: '#mainReleaseTrack', property: input.value('secondaryReleases'), }), { - dependencies: ['#mainRelease', '#mainRelease.secondaryReleases'], + dependencies: [ + '#mainReleaseTrack', + '#mainReleaseTrack.secondaryReleases', + ], + compute: (continuation, { - ['#mainRelease']: mainRelease, - ['#mainRelease.secondaryReleases']: secondaryReleases, + ['#mainReleaseTrack']: mainReleaseTrack, + ['#mainReleaseTrack.secondaryReleases']: secondaryReleases, }) => continuation({ ['#allReleases']: - sortByDate([mainRelease, ...secondaryReleases]), + sortByDate([mainReleaseTrack, ...secondaryReleases]), }), }, ], diff --git a/src/data/composite/things/track/withAlwaysReferenceByDirectory.js b/src/data/composite/things/track/withAlwaysReferenceByDirectory.js deleted file mode 100644 index 60faeaf4..00000000 --- a/src/data/composite/things/track/withAlwaysReferenceByDirectory.js +++ /dev/null @@ -1,97 +0,0 @@ -// Controls how find.track works - it'll never be matched by a reference -// just to the track's name, which means you don't have to always reference -// some *other* (much more commonly referenced) track by directory instead -// of more naturally by name. - -import {input, templateCompositeFrom} from '#composite'; -import find from '#find'; -import {isBoolean} from '#validators'; - -import {withPropertyFromObject} from '#composite/data'; -import {withResolvedReference} from '#composite/wiki-data'; -import {soupyFind} from '#composite/wiki-properties'; - -import { - exitWithoutDependency, - exposeDependencyOrContinue, - exposeUpdateValueOrContinue, -} from '#composite/control-flow'; - -import withPropertyFromAlbum from './withPropertyFromAlbum.js'; - -export default templateCompositeFrom({ - annotation: `withAlwaysReferenceByDirectory`, - - outputs: ['#alwaysReferenceByDirectory'], - - steps: () => [ - exposeUpdateValueOrContinue({ - validate: input.value(isBoolean), - }), - - withPropertyFromAlbum({ - property: input.value('alwaysReferenceTracksByDirectory'), - }), - - // Falsy mode means this exposes true if the album's property is true, - // but continues if the property is false (which is also the default). - exposeDependencyOrContinue({ - dependency: '#album.alwaysReferenceTracksByDirectory', - mode: input.value('falsy'), - }), - - // Remaining code is for defaulting to true if this track is a rerelease of - // another with the same name, so everything further depends on access to - // trackData as well as mainReleaseTrack. - - exitWithoutDependency({ - dependency: 'trackData', - mode: input.value('empty'), - value: input.value(false), - }), - - exitWithoutDependency({ - dependency: 'mainReleaseTrack', - value: input.value(false), - }), - - // It's necessary to use the custom trackMainReleasesOnly find function - // here, so as to avoid recursion issues - the find.track() function depends - // on accessing each track's alwaysReferenceByDirectory, which means it'll - // hit *this track* - and thus this step - and end up recursing infinitely. - // By definition, find.trackMainReleasesOnly excludes tracks which have - // an mainReleaseTrack update value set, which means even though it does - // still access each of tracks' `alwaysReferenceByDirectory` property, it - // won't access that of *this* track - it will never proceed past the - // `exitWithoutDependency` step directly above, so there's no opportunity - // for recursion. - withResolvedReference({ - ref: 'mainReleaseTrack', - data: 'trackData', - find: input.value(find.trackMainReleasesOnly), - }).outputs({ - '#resolvedReference': '#mainRelease', - }), - - exitWithoutDependency({ - dependency: '#mainRelease', - value: input.value(false), - }), - - withPropertyFromObject({ - object: '#mainRelease', - property: input.value('name'), - }), - - { - dependencies: ['name', '#mainRelease.name'], - compute: (continuation, { - name, - ['#mainRelease.name']: mainReleaseName, - }) => continuation({ - ['#alwaysReferenceByDirectory']: - name === mainReleaseName, - }), - }, - ], -}); diff --git a/src/data/composite/things/track/withDate.js b/src/data/composite/things/track/withDate.js index b5a770e9..1851c0d2 100644 --- a/src/data/composite/things/track/withDate.js +++ b/src/data/composite/things/track/withDate.js @@ -12,6 +12,14 @@ export default templateCompositeFrom({ steps: () => [ { + dependencies: ['disableDate'], + compute: (continuation, {disableDate}) => + (disableDate + ? continuation.raiseOutput({'#date': null}) + : continuation()), + }, + + { dependencies: ['dateFirstReleased'], compute: (continuation, {dateFirstReleased}) => (dateFirstReleased diff --git a/src/data/composite/things/track/withDirectorySuffix.js b/src/data/composite/things/track/withDirectorySuffix.js index c063e158..c3651491 100644 --- a/src/data/composite/things/track/withDirectorySuffix.js +++ b/src/data/composite/things/track/withDirectorySuffix.js @@ -1,8 +1,9 @@ import {input, templateCompositeFrom} from '#composite'; import {raiseOutputWithoutDependency} from '#composite/control-flow'; +import {withPropertyFromObject} from '#composite/data'; -import withPropertyFromAlbum from './withPropertyFromAlbum.js'; +import withContainingTrackSection from './withContainingTrackSection.js'; import withSuffixDirectoryFromAlbum from './withSuffixDirectoryFromAlbum.js'; export default templateCompositeFrom({ @@ -16,21 +17,16 @@ export default templateCompositeFrom({ raiseOutputWithoutDependency({ dependency: '#suffixDirectoryFromAlbum', mode: input.value('falsy'), - output: input.value({['#directorySuffix']: null}), + output: input.value({'#directorySuffix': null}), }), - withPropertyFromAlbum({ + withContainingTrackSection(), + + withPropertyFromObject({ + object: '#trackSection', property: input.value('directorySuffix'), + }).outputs({ + '#trackSection.directorySuffix': '#directorySuffix', }), - - { - dependencies: ['#album.directorySuffix'], - compute: (continuation, { - ['#album.directorySuffix']: directorySuffix, - }) => continuation({ - ['#directorySuffix']: - directorySuffix, - }), - }, ], }); diff --git a/src/data/composite/things/track/withMainRelease.js b/src/data/composite/things/track/withMainRelease.js index 3a91edae..67a312ae 100644 --- a/src/data/composite/things/track/withMainRelease.js +++ b/src/data/composite/things/track/withMainRelease.js @@ -1,13 +1,15 @@ -// Just includes the main release of this track as a dependency. -// If this track isn't a secondary release, then it'll provide null, unless -// the {selfIfMain} option is set, in which case it'll provide this track -// itself. This will early exit (with notFoundValue) if the main release -// is specified by reference and that reference doesn't resolve to anything. +// Resolves this track's `mainRelease` reference, using weird-ass atypical +// machinery that operates on soupyFind and does not operate on findMixed, +// let alone a prim and proper standalone find spec. +// +// Raises null only if there is no `mainRelease` reference provided at all. +// This will early exit (with notFoundValue) if the reference doesn't resolve. +// import {input, templateCompositeFrom} from '#composite'; -import {exitWithoutDependency, withResultOfAvailabilityCheck} - from '#composite/control-flow'; +import {raiseOutputWithoutDependency} from '#composite/control-flow'; +import {withPropertyFromObject} from '#composite/data'; import {withResolvedReference} from '#composite/wiki-data'; import {soupyFind} from '#composite/wiki-properties'; @@ -15,56 +17,121 @@ export default templateCompositeFrom({ annotation: `withMainRelease`, inputs: { - selfIfMain: input({type: 'boolean', defaultValue: false}), + from: input({ + defaultDependency: 'mainRelease', + acceptsNull: true, + }), + notFoundValue: input({defaultValue: null}), }, outputs: ['#mainRelease'], steps: () => [ - withResultOfAvailabilityCheck({ - from: 'mainReleaseTrack', + raiseOutputWithoutDependency({ + dependency: input('from'), + output: input.value({'#mainRelease': null}), }), { + dependencies: [input('from'), 'name'], + compute: (continuation, { + [input('from')]: ref, + ['name']: ownName, + }) => + (ref === 'same name single' + ? continuation({ + ['#albumOrTrackReference']: null, + ['#sameNameSingleReference']: ownName, + }) + : continuation({ + ['#albumOrTrackReference']: ref, + ['#sameNameSingleReference']: null, + })), + }, + + withResolvedReference({ + ref: '#albumOrTrackReference', + find: soupyFind.input('trackMainReleasesOnly'), + }).outputs({ + '#resolvedReference': '#matchingTrack', + }), + + withResolvedReference({ + ref: '#albumOrTrackReference', + find: soupyFind.input('album'), + }).outputs({ + '#resolvedReference': '#matchingAlbum', + }), + + withResolvedReference({ + ref: '#sameNameSingleReference', + find: soupyFind.input('albumSinglesOnly'), + findOptions: input.value({ + fuzz: { + capitalization: true, + kebab: true, + }, + }), + }).outputs({ + '#resolvedReference': '#sameNameSingle', + }), + + { + dependencies: ['#sameNameSingle'], + compute: (continuation, { + ['#sameNameSingle']: sameNameSingle, + }) => + (sameNameSingle + ? continuation.raiseOutput({ + ['#mainRelease']: + sameNameSingle, + }) + : continuation()), + }, + + { dependencies: [ - input.myself(), - input('selfIfMain'), - '#availability', + '#matchingTrack', + '#matchingAlbum', + input('notFoundValue'), ], compute: (continuation, { - [input.myself()]: track, - [input('selfIfMain')]: selfIfMain, - '#availability': availability, + ['#matchingTrack']: matchingTrack, + ['#matchingAlbum']: matchingAlbum, + [input('notFoundValue')]: notFoundValue, }) => - (availability + (matchingTrack && matchingAlbum ? continuation() - : continuation.raiseOutput({ + : matchingTrack ?? matchingAlbum + ? continuation.raiseOutput({ ['#mainRelease']: - (selfIfMain ? track : null), - })), + matchingTrack ?? matchingAlbum, + }) + : continuation.exit(notFoundValue)), }, - withResolvedReference({ - ref: 'mainReleaseTrack', - find: soupyFind.input('track'), - }), - - exitWithoutDependency({ - dependency: '#resolvedReference', - value: input('notFoundValue'), + withPropertyFromObject({ + object: '#matchingAlbum', + property: input.value('tracks'), }), { - dependencies: ['#resolvedReference'], + dependencies: [ + '#matchingAlbum.tracks', + '#matchingTrack', + input('notFoundValue'), + ], compute: (continuation, { - ['#resolvedReference']: resolvedReference, + ['#matchingAlbum.tracks']: matchingAlbumTracks, + ['#matchingTrack']: matchingTrack, + [input('notFoundValue')]: notFoundValue, }) => - continuation({ - ['#mainRelease']: resolvedReference, - }), + (matchingAlbumTracks.includes(matchingTrack) + ? continuation.raiseOutput({'#mainRelease': matchingTrack}) + : continuation.exit(notFoundValue)), }, ], }); diff --git a/src/data/composite/things/track/withMainReleaseTrack.js b/src/data/composite/things/track/withMainReleaseTrack.js new file mode 100644 index 00000000..6371e895 --- /dev/null +++ b/src/data/composite/things/track/withMainReleaseTrack.js @@ -0,0 +1,248 @@ +// Just provides the main release of this track as a dependency. +// If this track isn't a secondary release, then it'll provide null, unless +// the {selfIfMain} option is set, in which case it'll provide this track +// itself. This will early exit (with notFoundValue) if the main release +// is specified by reference and that reference doesn't resolve to anything. + +import {input, templateCompositeFrom} from '#composite'; +import {onlyItem} from '#sugar'; +import {getKebabCase} from '#wiki-data'; + +import { + exitWithoutDependency, + withAvailabilityFilter, + withResultOfAvailabilityCheck, +} from '#composite/control-flow'; + +import { + withFilteredList, + withMappedList, + withPropertyFromList, + withPropertyFromObject, +} from '#composite/data'; + +import withMainRelease from './withMainRelease.js'; + +export default templateCompositeFrom({ + annotation: `withMainReleaseTrack`, + + inputs: { + selfIfMain: input({type: 'boolean', defaultValue: false}), + notFoundValue: input({defaultValue: null}), + }, + + outputs: ['#mainReleaseTrack'], + + steps: () => [ + withResultOfAvailabilityCheck({ + from: 'mainRelease', + }), + + { + dependencies: [ + input.myself(), + input('selfIfMain'), + '#availability', + ], + + compute: (continuation, { + [input.myself()]: track, + [input('selfIfMain')]: selfIfMain, + '#availability': availability, + }) => + (availability + ? continuation() + : continuation.raiseOutput({ + ['#mainReleaseTrack']: + (selfIfMain ? track : null), + })), + }, + + withMainRelease(), + + exitWithoutDependency({ + dependency: '#mainRelease', + value: input('notFoundValue'), + }), + + withPropertyFromObject({ + object: '#mainRelease', + property: input.value('isTrack'), + }), + + { + dependencies: ['#mainRelease', '#mainRelease.isTrack'], + + compute: (continuation, { + ['#mainRelease']: mainRelease, + ['#mainRelease.isTrack']: mainReleaseIsTrack, + }) => + (mainReleaseIsTrack + ? continuation.raiseOutput({ + ['#mainReleaseTrack']: mainRelease, + }) + : continuation()), + }, + + { + dependencies: ['name', 'directory'], + compute: (continuation, { + ['name']: ownName, + ['directory']: ownDirectory, + }) => { + const ownNameKebabed = getKebabCase(ownName); + + return continuation({ + ['#mapItsNameLikeName']: + name => getKebabCase(name) === ownNameKebabed, + + ['#mapItsDirectoryLikeDirectory']: + (ownDirectory + ? directory => directory === ownDirectory + : () => false), + + ['#mapItsNameLikeDirectory']: + (ownDirectory + ? name => getKebabCase(name) === ownDirectory + : () => false), + + ['#mapItsDirectoryLikeName']: + directory => directory === ownNameKebabed, + }); + }, + }, + + withPropertyFromObject({ + object: '#mainRelease', + property: input.value('tracks'), + }), + + withPropertyFromList({ + list: '#mainRelease.tracks', + property: input.value('mainRelease'), + internal: input.value(true), + }), + + withAvailabilityFilter({ + from: '#mainRelease.tracks.mainRelease', + }), + + withMappedList({ + list: '#availabilityFilter', + map: input.value(item => !item), + }).outputs({ + '#mappedList': '#availabilityFilter', + }), + + withFilteredList({ + list: '#mainRelease.tracks', + filter: '#availabilityFilter', + }).outputs({ + '#filteredList': '#mainRelease.tracks', + }), + + withPropertyFromList({ + list: '#mainRelease.tracks', + property: input.value('name'), + }), + + withPropertyFromList({ + list: '#mainRelease.tracks', + property: input.value('directory'), + internal: input.value(true), + }), + + withMappedList({ + list: '#mainRelease.tracks.name', + map: '#mapItsNameLikeName', + }).outputs({ + '#mappedList': '#filterItsNameLikeName', + }), + + withMappedList({ + list: '#mainRelease.tracks.directory', + map: '#mapItsDirectoryLikeDirectory', + }).outputs({ + '#mappedList': '#filterItsDirectoryLikeDirectory', + }), + + withMappedList({ + list: '#mainRelease.tracks.name', + map: '#mapItsNameLikeDirectory', + }).outputs({ + '#mappedList': '#filterItsNameLikeDirectory', + }), + + withMappedList({ + list: '#mainRelease.tracks.directory', + map: '#mapItsDirectoryLikeName', + }).outputs({ + '#mappedList': '#filterItsDirectoryLikeName', + }), + + withFilteredList({ + list: '#mainRelease.tracks', + filter: '#filterItsNameLikeName', + }).outputs({ + '#filteredList': '#matchingItsNameLikeName', + }), + + withFilteredList({ + list: '#mainRelease.tracks', + filter: '#filterItsDirectoryLikeDirectory', + }).outputs({ + '#filteredList': '#matchingItsDirectoryLikeDirectory', + }), + + withFilteredList({ + list: '#mainRelease.tracks', + filter: '#filterItsNameLikeDirectory', + }).outputs({ + '#filteredList': '#matchingItsNameLikeDirectory', + }), + + withFilteredList({ + list: '#mainRelease.tracks', + filter: '#filterItsDirectoryLikeName', + }).outputs({ + '#filteredList': '#matchingItsDirectoryLikeName', + }), + + { + dependencies: [ + '#matchingItsNameLikeName', + '#matchingItsDirectoryLikeDirectory', + '#matchingItsNameLikeDirectory', + '#matchingItsDirectoryLikeName', + ], + + compute: (continuation, { + ['#matchingItsNameLikeName']: NLN, + ['#matchingItsDirectoryLikeDirectory']: DLD, + ['#matchingItsNameLikeDirectory']: NLD, + ['#matchingItsDirectoryLikeName']: DLN, + }) => continuation({ + ['#mainReleaseTrack']: + onlyItem(DLD) ?? + onlyItem(NLN) ?? + onlyItem(DLN) ?? + onlyItem(NLD) ?? + null, + }), + }, + + { + dependencies: ['#mainReleaseTrack', input.myself()], + + compute: (continuation, { + ['#mainReleaseTrack']: mainReleaseTrack, + [input.myself()]: thisTrack, + }) => continuation({ + ['#mainReleaseTrack']: + (mainReleaseTrack === thisTrack + ? null + : mainReleaseTrack), + }), + }, + ], +}); diff --git a/src/data/composite/things/track/withOtherReleases.js b/src/data/composite/things/track/withOtherReleases.js index 0639742f..bb3e8983 100644 --- a/src/data/composite/things/track/withOtherReleases.js +++ b/src/data/composite/things/track/withOtherReleases.js @@ -3,9 +3,6 @@ import {input, templateCompositeFrom} from '#composite'; -import {exitWithoutDependency} from '#composite/control-flow'; -import {withPropertyFromObject} from '#composite/data'; - import withAllReleases from './withAllReleases.js'; export default templateCompositeFrom({ diff --git a/src/data/composite/things/track/withPropertyFromMainRelease.js b/src/data/composite/things/track/withPropertyFromMainRelease.js index 393a4c63..c6f65653 100644 --- a/src/data/composite/things/track/withPropertyFromMainRelease.js +++ b/src/data/composite/things/track/withPropertyFromMainRelease.js @@ -10,10 +10,10 @@ import {input, templateCompositeFrom} from '#composite'; import {withResultOfAvailabilityCheck} from '#composite/control-flow'; import {withPropertyFromObject} from '#composite/data'; -import withMainRelease from './withMainRelease.js'; +import withMainReleaseTrack from './withMainReleaseTrack.js'; export default templateCompositeFrom({ - annotation: `inheritFromMainRelease`, + annotation: `withPropertyFromMainRelease`, inputs: { property: input({type: 'string'}), @@ -32,12 +32,12 @@ export default templateCompositeFrom({ : ['#mainReleaseValue'])), steps: () => [ - withMainRelease({ + withMainReleaseTrack({ notFoundValue: input('notFoundValue'), }), withResultOfAvailabilityCheck({ - from: '#mainRelease', + from: '#mainReleaseTrack', }), { @@ -61,7 +61,7 @@ export default templateCompositeFrom({ }, withPropertyFromObject({ - object: '#mainRelease', + object: '#mainReleaseTrack', property: input('property'), }), diff --git a/src/data/composite/things/track/withSuffixDirectoryFromAlbum.js b/src/data/composite/things/track/withSuffixDirectoryFromAlbum.js index 7159a3f4..30c777b6 100644 --- a/src/data/composite/things/track/withSuffixDirectoryFromAlbum.js +++ b/src/data/composite/things/track/withSuffixDirectoryFromAlbum.js @@ -1,8 +1,9 @@ import {input, templateCompositeFrom} from '#composite'; import {withResultOfAvailabilityCheck} from '#composite/control-flow'; +import {withPropertyFromObject} from '#composite/data'; -import withPropertyFromAlbum from './withPropertyFromAlbum.js'; +import withContainingTrackSection from './withContainingTrackSection.js'; export default templateCompositeFrom({ annotation: `withSuffixDirectoryFromAlbum`, @@ -36,18 +37,13 @@ export default templateCompositeFrom({ : continuation()), }, - withPropertyFromAlbum({ + withContainingTrackSection(), + + withPropertyFromObject({ + object: '#trackSection', property: input.value('suffixTrackDirectories'), + }).outputs({ + '#trackSection.suffixTrackDirectories': '#suffixDirectoryFromAlbum', }), - - { - dependencies: ['#album.suffixTrackDirectories'], - compute: (continuation, { - ['#album.suffixTrackDirectories']: suffixTrackDirectories, - }) => continuation({ - ['#suffixDirectoryFromAlbum']: - suffixTrackDirectories, - }), - }, ], }); diff --git a/src/data/composite/wiki-data/exitWithoutArtwork.js b/src/data/composite/wiki-data/exitWithoutArtwork.js new file mode 100644 index 00000000..8e799fda --- /dev/null +++ b/src/data/composite/wiki-data/exitWithoutArtwork.js @@ -0,0 +1,45 @@ +import {input, templateCompositeFrom} from '#composite'; +import {isContributionList, isThing, strictArrayOf} from '#validators'; + +import {exitWithoutDependency} from '#composite/control-flow'; + +import withHasArtwork from './withHasArtwork.js'; + +export default templateCompositeFrom({ + annotation: `exitWithoutArtwork`, + + inputs: { + contribs: input({ + validate: isContributionList, + defaultValue: null, + }), + + artwork: input({ + validate: isThing, + defaultValue: null, + }), + + artworks: input({ + validate: strictArrayOf(isThing), + defaultValue: null, + }), + + value: input({ + defaultValue: null, + }), + }, + + steps: () => [ + withHasArtwork({ + contribs: input('contribs'), + artwork: input('artwork'), + artworks: input('artworks'), + }), + + exitWithoutDependency({ + dependency: '#hasArtwork', + mode: input.value('falsy'), + value: input('value'), + }), + ], +}); diff --git a/src/data/composite/wiki-data/index.js b/src/data/composite/wiki-data/index.js index 005c68c0..d70d7c56 100644 --- a/src/data/composite/wiki-data/index.js +++ b/src/data/composite/wiki-data/index.js @@ -5,24 +5,27 @@ // export {default as exitWithoutContribs} from './exitWithoutContribs.js'; +export {default as exitWithoutArtwork} from './exitWithoutArtwork.js'; export {default as gobbleSoupyFind} from './gobbleSoupyFind.js'; export {default as gobbleSoupyReverse} from './gobbleSoupyReverse.js'; +export {default as inputFindOptions} from './inputFindOptions.js'; export {default as inputNotFoundMode} from './inputNotFoundMode.js'; export {default as inputSoupyFind} from './inputSoupyFind.js'; export {default as inputSoupyReverse} from './inputSoupyReverse.js'; export {default as inputWikiData} from './inputWikiData.js'; +export {default as splitContentNodesAround} from './splitContentNodesAround.js'; export {default as withClonedThings} from './withClonedThings.js'; export {default as withConstitutedArtwork} from './withConstitutedArtwork.js'; +export {default as withContentNodes} from './withContentNodes.js'; export {default as withContributionListSums} from './withContributionListSums.js'; -export {default as withCoverArtDate} from './withCoverArtDate.js'; export {default as withDirectory} from './withDirectory.js'; +export {default as withHasArtwork} from './withHasArtwork.js'; export {default as withRecontextualizedContributionList} from './withRecontextualizedContributionList.js'; export {default as withRedatedContributionList} from './withRedatedContributionList.js'; export {default as withResolvedAnnotatedReferenceList} from './withResolvedAnnotatedReferenceList.js'; export {default as withResolvedContribs} from './withResolvedContribs.js'; export {default as withResolvedReference} from './withResolvedReference.js'; export {default as withResolvedReferenceList} from './withResolvedReferenceList.js'; -export {default as withResolvedSeriesList} from './withResolvedSeriesList.js'; export {default as withReverseReferenceList} from './withReverseReferenceList.js'; export {default as withThingsSortedAlphabetically} from './withThingsSortedAlphabetically.js'; export {default as withUniqueReferencingThing} from './withUniqueReferencingThing.js'; diff --git a/src/data/composite/wiki-data/inputFindOptions.js b/src/data/composite/wiki-data/inputFindOptions.js new file mode 100644 index 00000000..07ed4bce --- /dev/null +++ b/src/data/composite/wiki-data/inputFindOptions.js @@ -0,0 +1,5 @@ +import {input} from '#composite'; + +export default function inputFindOptions() { + return input({type: 'object', defaultValue: null}); +} diff --git a/src/data/composite/wiki-data/splitContentNodesAround.js b/src/data/composite/wiki-data/splitContentNodesAround.js new file mode 100644 index 00000000..6648d8e1 --- /dev/null +++ b/src/data/composite/wiki-data/splitContentNodesAround.js @@ -0,0 +1,87 @@ +import {input, templateCompositeFrom} from '#composite'; +import {splitContentNodesAround} from '#replacer'; +import {anyOf, isFunction, validateInstanceOf} from '#validators'; + +import {withFilteredList, withMappedList, withUnflattenedList} + from '#composite/data'; + +export default templateCompositeFrom({ + annotation: `splitContentNodesAround`, + + inputs: { + nodes: input({type: 'array'}), + + around: input({ + validate: + anyOf(isFunction, validateInstanceOf(RegExp)), + }), + }, + + outputs: ['#contentNodeLists'], + + steps: () => [ + { + dependencies: [input('nodes'), input('around')], + + compute: (continuation, { + [input('nodes')]: nodes, + [input('around')]: splitter, + }) => continuation({ + ['#nodes']: + Array.from(splitContentNodesAround(nodes, splitter)), + }), + }, + + withMappedList({ + list: '#nodes', + map: input.value(node => node.type === 'separator'), + }).outputs({ + '#mappedList': '#separatorFilter', + }), + + withMappedList({ + list: '#separatorFilter', + filter: '#separatorFilter', + map: input.value((_node, index) => index), + }), + + withFilteredList({ + list: '#mappedList', + filter: '#separatorFilter', + }).outputs({ + '#filteredList': '#separatorIndices', + }), + + { + dependencies: ['#nodes', '#separatorFilter'], + + compute: (continuation, { + ['#nodes']: nodes, + ['#separatorFilter']: separatorFilter, + }) => continuation({ + ['#nodes']: + nodes.map((node, index) => + (separatorFilter[index] + ? null + : node)), + }), + }, + + { + dependencies: ['#separatorIndices'], + compute: (continuation, { + ['#separatorIndices']: separatorIndices, + }) => continuation({ + ['#unflattenIndices']: + [0, ...separatorIndices], + }), + }, + + withUnflattenedList({ + list: '#nodes', + indices: '#unflattenIndices', + }).outputs({ + '#unflattenedList': '#contentNodeLists', + }), + ], +}); diff --git a/src/data/composite/wiki-data/withConstitutedArtwork.js b/src/data/composite/wiki-data/withConstitutedArtwork.js index 6187d55b..28d719e2 100644 --- a/src/data/composite/wiki-data/withConstitutedArtwork.js +++ b/src/data/composite/wiki-data/withConstitutedArtwork.js @@ -1,6 +1,5 @@ import {input, templateCompositeFrom} from '#composite'; import thingConstructors from '#things'; -import {isContributionList} from '#validators'; export default templateCompositeFrom({ annotation: `withConstitutedArtwork`, diff --git a/src/data/composite/wiki-data/withContentNodes.js b/src/data/composite/wiki-data/withContentNodes.js new file mode 100644 index 00000000..d014d43b --- /dev/null +++ b/src/data/composite/wiki-data/withContentNodes.js @@ -0,0 +1,25 @@ +import {input, templateCompositeFrom} from '#composite'; +import {parseContentNodes} from '#replacer'; + +export default templateCompositeFrom({ + annotation: `withContentNodes`, + + inputs: { + from: input({type: 'string', acceptsNull: false}), + }, + + outputs: ['#contentNodes'], + + steps: () => [ + { + dependencies: [input('from')], + + compute: (continuation, { + [input('from')]: string, + }) => continuation({ + ['#contentNodes']: + parseContentNodes(string), + }), + }, + ], +}); diff --git a/src/data/composite/things/album/withHasCoverArt.js b/src/data/composite/wiki-data/withHasArtwork.js index fd3f2894..9c22f439 100644 --- a/src/data/composite/things/album/withHasCoverArt.js +++ b/src/data/composite/wiki-data/withHasArtwork.js @@ -1,7 +1,5 @@ -// TODO: This shouldn't be coded as an Album-specific thing, -// or even really to do with cover artworks in particular, either. - import {input, templateCompositeFrom} from '#composite'; +import {isContributionList, isThing, strictArrayOf} from '#validators'; import {raiseOutputWithoutDependency, withResultOfAvailabilityCheck} from '#composite/control-flow'; @@ -9,13 +7,30 @@ import {fillMissingListItems, withFlattenedList, withPropertyFromList} from '#composite/data'; export default templateCompositeFrom({ - annotation: 'withHasCoverArt', + annotation: 'withHasArtwork', + + inputs: { + contribs: input({ + validate: isContributionList, + defaultValue: null, + }), + + artwork: input({ + validate: isThing, + defaultValue: null, + }), + + artworks: input({ + validate: strictArrayOf(isThing), + defaultValue: null, + }), + }, - outputs: ['#hasCoverArt'], + outputs: ['#hasArtwork'], steps: () => [ withResultOfAvailabilityCheck({ - from: 'coverArtistContribs', + from: input('contribs'), mode: input.value('empty'), }), @@ -26,19 +41,37 @@ export default templateCompositeFrom({ }) => (availability ? continuation.raiseOutput({ - ['#hasCoverArt']: true, + ['#hasArtwork']: true, }) : continuation()), }, + { + dependencies: [input('artwork'), input('artworks')], + compute: (continuation, { + [input('artwork')]: artwork, + [input('artworks')]: artworks, + }) => + continuation({ + ['#artworks']: + (artwork && artworks + ? [artwork, ...artworks] + : artwork + ? [artwork] + : artworks + ? artworks + : []), + }), + }, + raiseOutputWithoutDependency({ - dependency: 'coverArtworks', + dependency: '#artworks', mode: input.value('empty'), - output: input.value({'#hasCoverArt': false}), + output: input.value({'#hasArtwork': false}), }), withPropertyFromList({ - list: 'coverArtworks', + list: '#artworks', property: input.value('artistContribs'), internal: input.value(true), }), @@ -46,19 +79,19 @@ export default templateCompositeFrom({ // Since we're getting the update value for each artwork's artistContribs, // it may not be set at all, and in that case won't be exposing as []. fillMissingListItems({ - list: '#coverArtworks.artistContribs', + list: '#artworks.artistContribs', fill: input.value([]), }), withFlattenedList({ - list: '#coverArtworks.artistContribs', + list: '#artworks.artistContribs', }), withResultOfAvailabilityCheck({ from: '#flattenedList', mode: input.value('empty'), }).outputs({ - '#availability': '#hasCoverArt', + '#availability': '#hasArtwork', }), ], }); diff --git a/src/data/composite/wiki-data/withResolvedAnnotatedReferenceList.js b/src/data/composite/wiki-data/withResolvedAnnotatedReferenceList.js index 9cc52f29..670dc422 100644 --- a/src/data/composite/wiki-data/withResolvedAnnotatedReferenceList.js +++ b/src/data/composite/wiki-data/withResolvedAnnotatedReferenceList.js @@ -7,6 +7,7 @@ import {withPropertyFromList} from '#composite/data'; import {raiseOutputWithoutDependency, withAvailabilityFilter} from '#composite/control-flow'; +import inputFindOptions from './inputFindOptions.js'; import inputSoupyFind from './inputSoupyFind.js'; import inputNotFoundMode from './inputNotFoundMode.js'; import inputWikiData from './inputWikiData.js'; @@ -28,6 +29,7 @@ export default templateCompositeFrom({ data: inputWikiData({allowMixedTypes: true}), find: inputSoupyFind(), + findOptions: inputFindOptions(), notFoundMode: inputNotFoundMode(), }, @@ -61,6 +63,7 @@ export default templateCompositeFrom({ list: '#references', data: input('data'), find: input('find'), + findOptions: input('findOptions'), notFoundMode: input.value('null'), }), diff --git a/src/data/composite/wiki-data/withResolvedReference.js b/src/data/composite/wiki-data/withResolvedReference.js index 6f422194..d9a05367 100644 --- a/src/data/composite/wiki-data/withResolvedReference.js +++ b/src/data/composite/wiki-data/withResolvedReference.js @@ -8,6 +8,7 @@ import {input, templateCompositeFrom} from '#composite'; import {raiseOutputWithoutDependency} from '#composite/control-flow'; import gobbleSoupyFind from './gobbleSoupyFind.js'; +import inputFindOptions from './inputFindOptions.js'; import inputSoupyFind from './inputSoupyFind.js'; import inputWikiData from './inputWikiData.js'; @@ -17,8 +18,9 @@ export default templateCompositeFrom({ inputs: { ref: input({type: 'string', acceptsNull: true}), - data: inputWikiData({allowMixedTypes: false}), + data: inputWikiData({allowMixedTypes: true}), find: inputSoupyFind(), + findOptions: inputFindOptions(), }, outputs: ['#resolvedReference'], @@ -36,21 +38,35 @@ export default templateCompositeFrom({ }), { + dependencies: [input('findOptions')], + compute: (continuation, { + [input('findOptions')]: findOptions, + }) => continuation({ + ['#findOptions']: + (findOptions + ? {...findOptions, mode: 'quiet'} + : {mode: 'quiet'}), + }), + }, + + { dependencies: [ input('ref'), input('data'), '#find', + '#findOptions', ], compute: (continuation, { [input('ref')]: ref, [input('data')]: data, ['#find']: findFunction, + ['#findOptions']: findOptions, }) => continuation({ ['#resolvedReference']: (data - ? findFunction(ref, data, {mode: 'quiet'}) ?? null - : findFunction(ref, {mode: 'quiet'}) ?? null), + ? findFunction(ref, data, findOptions) ?? null + : findFunction(ref, findOptions) ?? null), }), }, ], diff --git a/src/data/composite/wiki-data/withResolvedReferenceList.js b/src/data/composite/wiki-data/withResolvedReferenceList.js index 9dc960dd..14ce6919 100644 --- a/src/data/composite/wiki-data/withResolvedReferenceList.js +++ b/src/data/composite/wiki-data/withResolvedReferenceList.js @@ -11,6 +11,7 @@ import {raiseOutputWithoutDependency, withAvailabilityFilter} import {withMappedList} from '#composite/data'; import gobbleSoupyFind from './gobbleSoupyFind.js'; +import inputFindOptions from './inputFindOptions.js'; import inputNotFoundMode from './inputNotFoundMode.js'; import inputSoupyFind from './inputSoupyFind.js'; import inputWikiData from './inputWikiData.js'; @@ -27,6 +28,7 @@ export default templateCompositeFrom({ data: inputWikiData({allowMixedTypes: true}), find: inputSoupyFind(), + findOptions: inputFindOptions(), notFoundMode: inputNotFoundMode(), }, @@ -47,15 +49,28 @@ export default templateCompositeFrom({ }), { - dependencies: [input('data'), '#find'], + dependencies: [input('findOptions')], + compute: (continuation, { + [input('findOptions')]: findOptions, + }) => continuation({ + ['#findOptions']: + (findOptions + ? {...findOptions, mode: 'quiet'} + : {mode: 'quiet'}), + }), + }, + + { + dependencies: [input('data'), '#find', '#findOptions'], compute: (continuation, { [input('data')]: data, ['#find']: findFunction, + ['#findOptions']: findOptions, }) => continuation({ ['#map']: (data - ? ref => findFunction(ref, data, {mode: 'quiet'}) - : ref => findFunction(ref, {mode: 'quiet'})), + ? ref => findFunction(ref, data, findOptions) + : ref => findFunction(ref, findOptions)), }), }, diff --git a/src/data/composite/wiki-data/withResolvedSeriesList.js b/src/data/composite/wiki-data/withResolvedSeriesList.js deleted file mode 100644 index deaab466..00000000 --- a/src/data/composite/wiki-data/withResolvedSeriesList.js +++ /dev/null @@ -1,130 +0,0 @@ -import {input, templateCompositeFrom} from '#composite'; -import {stitchArrays} from '#sugar'; -import {isSeriesList, validateThing} from '#validators'; - -import {raiseOutputWithoutDependency} from '#composite/control-flow'; - -import { - fillMissingListItems, - withFlattenedList, - withUnflattenedList, - withPropertiesFromList, -} from '#composite/data'; - -import inputSoupyFind from './inputSoupyFind.js'; -import withResolvedReferenceList from './withResolvedReferenceList.js'; - -export default templateCompositeFrom({ - annotation: `withResolvedSeriesList`, - - inputs: { - group: input({ - validate: validateThing({referenceType: 'group'}), - }), - - list: input({ - validate: isSeriesList, - acceptsNull: true, - }), - }, - - outputs: ['#resolvedSeriesList'], - - steps: () => [ - raiseOutputWithoutDependency({ - dependency: input('list'), - mode: input.value('empty'), - output: input.value({ - ['#resolvedSeriesList']: [], - }), - }), - - withPropertiesFromList({ - list: input('list'), - prefix: input.value('#serieses'), - properties: input.value([ - 'name', - 'description', - 'albums', - - 'showAlbumArtists', - ]), - }), - - fillMissingListItems({ - list: '#serieses.albums', - fill: input.value([]), - }), - - withFlattenedList({ - list: '#serieses.albums', - }), - - withResolvedReferenceList({ - list: '#flattenedList', - find: inputSoupyFind.input('album'), - notFoundMode: input.value('null'), - }), - - withUnflattenedList({ - list: '#resolvedReferenceList', - }).outputs({ - '#unflattenedList': '#serieses.albums', - }), - - fillMissingListItems({ - list: '#serieses.description', - fill: input.value(null), - }), - - fillMissingListItems({ - list: '#serieses.showAlbumArtists', - fill: input.value(null), - }), - - { - dependencies: [ - '#serieses.name', - '#serieses.description', - '#serieses.albums', - - '#serieses.showAlbumArtists', - ], - - compute: (continuation, { - ['#serieses.name']: name, - ['#serieses.description']: description, - ['#serieses.albums']: albums, - - ['#serieses.showAlbumArtists']: showAlbumArtists, - }) => continuation({ - ['#seriesProperties']: - stitchArrays({ - name, - description, - albums, - - showAlbumArtists, - }).map(properties => ({ - ...properties, - group: input - })) - }), - }, - - { - dependencies: ['#seriesProperties', input('group')], - compute: (continuation, { - ['#seriesProperties']: seriesProperties, - [input('group')]: group, - }) => continuation({ - ['#resolvedSeriesList']: - seriesProperties - .map(properties => ({ - ...properties, - group, - })), - }), - }, - ], -}); diff --git a/src/data/composite/wiki-properties/additionalFiles.js b/src/data/composite/wiki-properties/additionalFiles.js deleted file mode 100644 index 6760527a..00000000 --- a/src/data/composite/wiki-properties/additionalFiles.js +++ /dev/null @@ -1,30 +0,0 @@ -// This is a somewhat more involved data structure - it's for additional -// or "bonus" files associated with albums or tracks (or anything else). -// It's got this form: -// -// [ -// {title: 'Booklet', files: ['Booklet.pdf']}, -// { -// title: 'Wallpaper', -// description: 'Cool Wallpaper!', -// files: ['1440x900.png', '1920x1080.png'] -// }, -// {title: 'Alternate Covers', description: null, files: [...]}, -// ... -// ] -// - -import {isAdditionalFileList} from '#validators'; - -// TODO: Not templateCompositeFrom. - -export default function() { - return { - flags: {update: true, expose: true}, - update: {validate: isAdditionalFileList}, - expose: { - transform: (additionalFiles) => - additionalFiles ?? [], - }, - }; -} diff --git a/src/data/composite/wiki-properties/additionalNameList.js b/src/data/composite/wiki-properties/additionalNameList.js deleted file mode 100644 index c5971d4a..00000000 --- a/src/data/composite/wiki-properties/additionalNameList.js +++ /dev/null @@ -1,14 +0,0 @@ -// A list of additional names! These can be used for a variety of purposes, -// e.g. providing extra searchable titles, localizations, romanizations or -// original titles, and so on. Each item has a name and, optionally, a -// descriptive annotation. - -import {isAdditionalNameList} from '#validators'; - -export default function() { - return { - flags: {update: true, expose: true}, - update: {validate: isAdditionalNameList}, - expose: {transform: value => value ?? []}, - }; -} diff --git a/src/data/composite/wiki-properties/annotatedReferenceList.js b/src/data/composite/wiki-properties/annotatedReferenceList.js index 8e6c96a1..aea0f22c 100644 --- a/src/data/composite/wiki-properties/annotatedReferenceList.js +++ b/src/data/composite/wiki-properties/annotatedReferenceList.js @@ -9,8 +9,13 @@ import { } from '#validators'; import {exposeDependency} from '#composite/control-flow'; -import {inputSoupyFind, inputWikiData, withResolvedAnnotatedReferenceList} - from '#composite/wiki-data'; + +import { + inputFindOptions, + inputSoupyFind, + inputWikiData, + withResolvedAnnotatedReferenceList, +} from '#composite/wiki-data'; import {referenceListInputDescriptions, referenceListUpdateDescription} from './helpers/reference-list-helpers.js'; @@ -25,6 +30,7 @@ export default templateCompositeFrom({ data: inputWikiData({allowMixedTypes: true}), find: inputSoupyFind(), + findOptions: inputFindOptions(), reference: input.staticValue({type: 'string', defaultValue: 'reference'}), annotation: input.staticValue({type: 'string', defaultValue: 'annotation'}), @@ -57,6 +63,7 @@ export default templateCompositeFrom({ data: input('data'), find: input('find'), + findOptions: input('findOptions'), }), exposeDependency({dependency: '#resolvedAnnotatedReferenceList'}), diff --git a/src/data/composite/wiki-properties/canonicalBase.js b/src/data/composite/wiki-properties/canonicalBase.js new file mode 100644 index 00000000..81740d6c --- /dev/null +++ b/src/data/composite/wiki-properties/canonicalBase.js @@ -0,0 +1,16 @@ +import {isURL} from '#validators'; + +export default function() { + return { + flags: {update: true, expose: true}, + update: {validate: isURL}, + expose: { + transform: (value) => + (value === null + ? null + : value.endsWith('/') + ? value + : value + '/'), + }, + }; +} diff --git a/src/data/composite/wiki-properties/index.js b/src/data/composite/wiki-properties/index.js index d5e7657e..57a2b8f2 100644 --- a/src/data/composite/wiki-properties/index.js +++ b/src/data/composite/wiki-properties/index.js @@ -3,9 +3,8 @@ // Entries here may depend on entries in #composite/control-flow, // #composite/data, and #composite/wiki-data. -export {default as additionalFiles} from './additionalFiles.js'; -export {default as additionalNameList} from './additionalNameList.js'; export {default as annotatedReferenceList} from './annotatedReferenceList.js'; +export {default as canonicalBase} from './canonicalBase.js'; export {default as color} from './color.js'; export {default as commentatorArtists} from './commentatorArtists.js'; export {default as constitutibleArtwork} from './constitutibleArtwork.js'; @@ -23,7 +22,6 @@ export {default as name} from './name.js'; export {default as referenceList} from './referenceList.js'; export {default as referencedArtworkList} from './referencedArtworkList.js'; export {default as reverseReferenceList} from './reverseReferenceList.js'; -export {default as seriesList} from './seriesList.js'; export {default as simpleDate} from './simpleDate.js'; export {default as simpleString} from './simpleString.js'; export {default as singleReference} from './singleReference.js'; diff --git a/src/data/composite/wiki-properties/referenceList.js b/src/data/composite/wiki-properties/referenceList.js index 4f8207b5..663349ee 100644 --- a/src/data/composite/wiki-properties/referenceList.js +++ b/src/data/composite/wiki-properties/referenceList.js @@ -11,8 +11,13 @@ import {input, templateCompositeFrom} from '#composite'; import {validateReferenceList} from '#validators'; import {exposeDependency} from '#composite/control-flow'; -import {inputSoupyFind, inputWikiData, withResolvedReferenceList} - from '#composite/wiki-data'; + +import { + inputFindOptions, + inputSoupyFind, + inputWikiData, + withResolvedReferenceList, +} from '#composite/wiki-data'; import {referenceListInputDescriptions, referenceListUpdateDescription} from './helpers/reference-list-helpers.js'; @@ -27,6 +32,7 @@ export default templateCompositeFrom({ data: inputWikiData({allowMixedTypes: true}), find: inputSoupyFind(), + findOptions: inputFindOptions(), }, update: @@ -39,6 +45,7 @@ export default templateCompositeFrom({ list: input.updateValue(), data: input('data'), find: input('find'), + findOptions: input('findOptions'), }), exposeDependency({dependency: '#resolvedReferenceList'}), diff --git a/src/data/composite/wiki-properties/referencedArtworkList.js b/src/data/composite/wiki-properties/referencedArtworkList.js index 9ba2e393..4f243493 100644 --- a/src/data/composite/wiki-properties/referencedArtworkList.js +++ b/src/data/composite/wiki-properties/referencedArtworkList.js @@ -1,6 +1,5 @@ import {input, templateCompositeFrom} from '#composite'; import find from '#find'; -import {isDate} from '#validators'; import annotatedReferenceList from './annotatedReferenceList.js'; diff --git a/src/data/composite/wiki-properties/seriesList.js b/src/data/composite/wiki-properties/seriesList.js deleted file mode 100644 index 2a101b45..00000000 --- a/src/data/composite/wiki-properties/seriesList.js +++ /dev/null @@ -1,31 +0,0 @@ -import {input, templateCompositeFrom} from '#composite'; -import {isSeriesList, validateThing} from '#validators'; - -import {exposeDependency} from '#composite/control-flow'; -import {withResolvedSeriesList} from '#composite/wiki-data'; - -export default templateCompositeFrom({ - annotation: `seriesList`, - - compose: false, - - inputs: { - group: input({ - validate: validateThing({referenceType: 'group'}), - }), - }, - - steps: () => [ - withResolvedSeriesList({ - group: input('group'), - - list: input.updateValue({ - validate: isSeriesList, - }), - }), - - exposeDependency({ - dependency: '#resolvedSeriesList', - }), - ], -}); diff --git a/src/data/composite/wiki-properties/singleReference.js b/src/data/composite/wiki-properties/singleReference.js index f532ebbe..25b97907 100644 --- a/src/data/composite/wiki-properties/singleReference.js +++ b/src/data/composite/wiki-properties/singleReference.js @@ -8,11 +8,19 @@ // import {input, templateCompositeFrom} from '#composite'; -import {isThingClass, validateReference} from '#validators'; +import {validateReference} from '#validators'; import {exposeDependency} from '#composite/control-flow'; -import {inputSoupyFind, inputWikiData, withResolvedReference} - from '#composite/wiki-data'; + +import { + inputFindOptions, + inputSoupyFind, + inputWikiData, + withResolvedReference, +} from '#composite/wiki-data'; + +import {referenceListInputDescriptions, referenceListUpdateDescription} + from './helpers/reference-list-helpers.js'; export default templateCompositeFrom({ annotation: `singleReference`, @@ -20,25 +28,24 @@ export default templateCompositeFrom({ compose: false, inputs: { - class: input.staticValue({validate: isThingClass}), + ...referenceListInputDescriptions(), + data: inputWikiData({allowMixedTypes: true}), find: inputSoupyFind(), - data: inputWikiData({allowMixedTypes: false}), + findOptions: inputFindOptions(), }, - update: ({ - [input.staticValue('class')]: thingClass, - }) => ({ - validate: - validateReference( - thingClass[Symbol.for('Thing.referenceType')]), - }), + update: + referenceListUpdateDescription({ + validateReferenceList: validateReference, + }), steps: () => [ withResolvedReference({ ref: input.updateValue(), data: input('data'), find: input('find'), + findOptions: input('findOptions'), }), exposeDependency({dependency: '#resolvedReference'}), diff --git a/src/data/language.js b/src/data/language.js index 3edf7e51..e97267c0 100644 --- a/src/data/language.js +++ b/src/data/language.js @@ -4,12 +4,10 @@ import path from 'node:path'; import {fileURLToPath} from 'node:url'; import chokidar from 'chokidar'; -import he from 'he'; // It stands for "HTML Entities", apparently. Cursed. import yaml from 'js-yaml'; import {annotateError, annotateErrorWithFile, showAggregate, withAggregate} from '#aggregate'; -import {externalLinkSpec} from '#external-links'; import {colors, logWarn} from '#cli'; import {empty, splitKeys, withEntries} from '#sugar'; import T from '#things'; @@ -248,19 +246,8 @@ async function processLanguageSpecFromFile(file, processLanguageSpecOpts) { } } -export function initializeLanguageObject() { - const language = new Language(); - - language.escapeHTML = string => - he.encode(string, {useNamedReferences: true}); - - language.externalLinkSpec = externalLinkSpec; - - return language; -} - export async function processLanguageFile(file) { - const language = initializeLanguageObject(); + const language = new Language() const properties = await processLanguageSpecFromFile(file); return Object.assign(language, properties); } @@ -271,7 +258,7 @@ export function watchLanguageFile(file, { const basename = path.basename(file); const events = new EventEmitter(); - const language = initializeLanguageObject(); + const language = new Language(); let emittedReady = false; let successfullyAppliedLanguage = false; diff --git a/src/data/thing.js b/src/data/thing.js index 66f73de5..f719224d 100644 --- a/src/data/thing.js +++ b/src/data/thing.js @@ -60,7 +60,7 @@ export default class Thing extends CacheableObject { if (this.name) { name = colors.green(`"${this.name}"`); } - } catch (error) { + } catch { name = colors.yellow(`couldn't get name`); } @@ -69,7 +69,7 @@ export default class Thing extends CacheableObject { if (this.directory) { reference = colors.blue(Thing.getReference(this)); } - } catch (error) { + } catch { reference = colors.yellow(`couldn't get reference`); } diff --git a/src/data/things/additional-file.js b/src/data/things/additional-file.js new file mode 100644 index 00000000..b15f62e0 --- /dev/null +++ b/src/data/things/additional-file.js @@ -0,0 +1,54 @@ +import {input} from '#composite'; +import Thing from '#thing'; +import {isString, validateArrayItems} from '#validators'; + +import {exposeConstant, exposeUpdateValueOrContinue} + from '#composite/control-flow'; +import {contentString, simpleString, thing} from '#composite/wiki-properties'; + +export class AdditionalFile extends Thing { + static [Thing.getPropertyDescriptors] = () => ({ + // Update & expose + + thing: thing(), + + title: simpleString(), + + description: contentString(), + + filenames: [ + exposeUpdateValueOrContinue({ + validate: input.value(validateArrayItems(isString)), + }), + + exposeConstant({ + value: input.value([]), + }), + ], + + // Expose only + + isAdditionalFile: [ + exposeConstant({ + value: input.value(true), + }), + ], + }); + + static [Thing.yamlDocumentSpec] = { + fields: { + 'Title': {property: 'title'}, + 'Description': {property: 'description'}, + 'Files': {property: 'filenames'}, + }, + }; + + get paths() { + if (!this.thing) return null; + if (!this.thing.getOwnAdditionalFilePath) return null; + + return ( + this.filenames.map(filename => + this.thing.getOwnAdditionalFilePath(this, filename))); + } +} diff --git a/src/data/things/additional-name.js b/src/data/things/additional-name.js new file mode 100644 index 00000000..99f3ee46 --- /dev/null +++ b/src/data/things/additional-name.js @@ -0,0 +1,31 @@ +import {input} from '#composite'; +import Thing from '#thing'; + +import {exposeConstant} from '#composite/control-flow'; +import {contentString, thing} from '#composite/wiki-properties'; + +export class AdditionalName extends Thing { + static [Thing.getPropertyDescriptors] = () => ({ + // Update & expose + + thing: thing(), + + name: contentString(), + annotation: contentString(), + + // Expose only + + isAdditionalName: [ + exposeConstant({ + value: input.value(true), + }), + ], + }); + + static [Thing.yamlDocumentSpec] = { + fields: { + 'Name': {property: 'name'}, + 'Annotation': {property: 'annotation'}, + }, + }; +} diff --git a/src/data/things/album.js b/src/data/things/album.js index c71b9820..58d5253c 100644 --- a/src/data/things/album.js +++ b/src/data/things/album.js @@ -8,9 +8,18 @@ import {colors} from '#cli'; import {input} from '#composite'; import {traverse} from '#node-utils'; import {sortAlbumsTracksChronologically, sortChronologically} from '#sort'; -import {accumulateSum, empty} from '#sugar'; +import {empty} from '#sugar'; import Thing from '#thing'; -import {isColor, isDate, isDirectory, isNumber} from '#validators'; + +import { + is, + isBoolean, + isColor, + isContributionList, + isDate, + isDirectory, + isNumber, +} from '#validators'; import { parseAdditionalFiles, @@ -25,16 +34,24 @@ import { parseWallpaperParts, } from '#yaml'; -import {exitWithoutDependency, exposeDependency, exposeUpdateValueOrContinue} - from '#composite/control-flow'; import {withPropertyFromObject} from '#composite/data'; -import {exitWithoutContribs, withDirectory, withCoverArtDate} - from '#composite/wiki-data'; +import { + exitWithoutDependency, + exposeConstant, + exposeDependency, + exposeDependencyOrContinue, + exposeUpdateValueOrContinue, +} from '#composite/control-flow'; + +import { + exitWithoutArtwork, + withDirectory, + withHasArtwork, + withResolvedContribs, +} from '#composite/wiki-data'; import { - additionalFiles, - additionalNameList, color, commentatorArtists, constitutibleArtwork, @@ -49,7 +66,6 @@ import { name, referencedArtworkList, referenceList, - reverseReferenceList, simpleDate, simpleString, soupyFind, @@ -61,7 +77,7 @@ import { wikiData, } from '#composite/wiki-properties'; -import {withHasCoverArt, withTracks} from '#composite/things/album'; +import {withCoverArtDate, withTracks} from '#composite/things/album'; import {withAlbum, withContinueCountingFrom, withStartCountingFrom} from '#composite/things/track-section'; @@ -69,16 +85,23 @@ export class Album extends Thing { static [Thing.referenceType] = 'album'; static [Thing.getPropertyDescriptors] = ({ + AdditionalFile, + AdditionalName, ArtTag, Artwork, CommentaryEntry, CreditingSourcesEntry, Group, - Track, TrackSection, WikiInfo, }) => ({ - // Update & expose + // > Update & expose - Internal relationships + + trackSections: thingList({ + class: input.value(TrackSection), + }), + + // > Update & expose - Identifying metadata name: name('Unnamed Album'), directory: directory(), @@ -99,18 +122,109 @@ export class Album extends Thing { alwaysReferenceTracksByDirectory: flag(false), suffixTrackDirectories: flag(false), - color: color(), - urls: urls(), + style: [ + exposeUpdateValueOrContinue({ + validate: input.value(is(...[ + 'album', + 'single', + ])), + }), - additionalNames: additionalNameList(), + exposeConstant({ + value: input.value('album'), + }), + ], bandcampAlbumIdentifier: simpleString(), bandcampArtworkIdentifier: simpleString(), + additionalNames: thingList({ + class: input.value(AdditionalName), + }), + date: simpleDate(), - trackArtDate: simpleDate(), dateAddedToWiki: simpleDate(), + // > Update & expose - Credits and contributors + + artistContribs: contributionList({ + date: 'date', + artistProperty: input.value('albumArtistContributions'), + }), + + trackArtistText: contentString(), + + trackArtistContribs: [ + withResolvedContribs({ + from: input.updateValue({validate: isContributionList}), + thingProperty: input.thisProperty(), + artistProperty: input.value('albumTrackArtistContributions'), + date: 'date', + }).outputs({ + '#resolvedContribs': '#trackArtistContribs', + }), + + exposeDependencyOrContinue({ + dependency: '#trackArtistContribs', + mode: input.value('empty'), + }), + + withResolvedContribs({ + from: 'artistContribs', + thingProperty: input.thisProperty(), + artistProperty: input.value('albumTrackArtistContributions'), + date: 'date', + }).outputs({ + '#resolvedContribs': '#trackArtistContribs', + }), + + exposeDependency({dependency: '#trackArtistContribs'}), + ], + + // > Update & expose - General configuration + + countTracksInArtistTotals: flag(true), + + showAlbumInTracksWithoutArtists: flag(false), + + hasTrackNumbers: flag(true), + isListedOnHomepage: flag(true), + isListedInGalleries: flag(true), + + hideDuration: flag(false), + + // > Update & expose - General metadata + + color: color(), + + urls: urls(), + + // > Update & expose - Artworks + + coverArtworks: [ + // This works, lol, because this array describes `expose.transform` for + // the coverArtworks property, and compositions generally access the + // update value, not what's exposed by property access out in the open. + // There's no recursion going on here. + exitWithoutArtwork({ + contribs: 'coverArtistContribs', + artworks: 'coverArtworks', + value: input.value([]), + }), + + constitutibleArtworkList.fromYAMLFieldSpec + .call(this, 'Cover Artwork'), + ], + + coverArtistContribs: [ + withCoverArtDate(), + + contributionList({ + date: '#coverArtDate', + artistProperty: input.value('albumCoverArtistContributions'), + }), + ], + coverArtDate: [ withCoverArtDate({ from: input.updateValue({ @@ -122,52 +236,61 @@ export class Album extends Thing { ], coverArtFileExtension: [ - exitWithoutContribs({contribs: 'coverArtistContribs'}), + exitWithoutArtwork({ + contribs: 'coverArtistContribs', + artworks: 'coverArtworks', + }), + fileExtension('jpg'), ], - trackCoverArtFileExtension: fileExtension('jpg'), + coverArtDimensions: [ + exitWithoutArtwork({ + contribs: 'coverArtistContribs', + artworks: 'coverArtworks', + }), - wallpaperFileExtension: [ - exitWithoutContribs({contribs: 'wallpaperArtistContribs'}), - fileExtension('jpg'), + dimensions(), ], - bannerFileExtension: [ - exitWithoutContribs({contribs: 'bannerArtistContribs'}), - fileExtension('jpg'), - ], + artTags: [ + exitWithoutArtwork({ + contribs: 'coverArtistContribs', + artworks: 'coverArtworks', + value: input.value([]), + }), - wallpaperStyle: [ - exitWithoutContribs({contribs: 'wallpaperArtistContribs'}), - simpleString(), + referenceList({ + class: input.value(ArtTag), + find: soupyFind.input('artTag'), + }), ], - wallpaperParts: [ - exitWithoutContribs({ - contribs: 'wallpaperArtistContribs', + referencedArtworks: [ + exitWithoutArtwork({ + contribs: 'coverArtistContribs', + artworks: 'coverArtworks', value: input.value([]), }), - wallpaperParts(), + referencedArtworkList(), ], - bannerStyle: [ - exitWithoutContribs({contribs: 'bannerArtistContribs'}), - simpleString(), - ], + trackCoverArtistContribs: contributionList({ + // May be null, indicating cover art was added for tracks on the date + // each track specifies, or else the track's own release date. + date: 'trackArtDate', - coverArtDimensions: [ - exitWithoutContribs({contribs: 'coverArtistContribs'}), - dimensions(), - ], + // This is the "correct" value, but it gets overwritten - with the same + // value - regardless. + artistProperty: input.value('trackCoverArtistContributions'), + }), - trackDimensions: dimensions(), + trackArtDate: simpleDate(), - bannerDimensions: [ - exitWithoutContribs({contribs: 'bannerArtistContribs'}), - dimensions(), - ], + trackCoverArtFileExtension: fileExtension('jpg'), + + trackDimensions: dimensions(), wallpaperArtwork: [ exitWithoutDependency({ @@ -180,117 +303,115 @@ export class Album extends Thing { .call(this, 'Wallpaper Artwork'), ], - bannerArtwork: [ - exitWithoutDependency({ - dependency: 'bannerArtistContribs', - mode: input.value('empty'), - value: input.value(null), - }), + wallpaperArtistContribs: [ + withCoverArtDate(), - constitutibleArtwork.fromYAMLFieldSpec - .call(this, 'Banner Artwork'), + contributionList({ + date: '#coverArtDate', + artistProperty: input.value('albumWallpaperArtistContributions'), + }), ], - coverArtworks: [ - withHasCoverArt(), - - exitWithoutDependency({ - dependency: '#hasCoverArt', - mode: input.value('falsy'), - value: input.value([]), + wallpaperFileExtension: [ + exitWithoutArtwork({ + contribs: 'wallpaperArtistContribs', + artwork: 'wallpaperArtwork', }), - constitutibleArtworkList.fromYAMLFieldSpec - .call(this, 'Cover Artwork'), + fileExtension('jpg'), ], - hasTrackNumbers: flag(true), - isListedOnHomepage: flag(true), - isListedInGalleries: flag(true), + wallpaperStyle: [ + exitWithoutArtwork({ + contribs: 'wallpaperArtistContribs', + artwork: 'wallpaperArtwork', + }), - commentary: thingList({ - class: input.value(CommentaryEntry), - }), + simpleString(), + ], - creditSources: thingList({ - class: input.value(CreditingSourcesEntry), - }), + wallpaperParts: [ + // kinda nonsensical or at least unlikely lol, but y'know + exitWithoutArtwork({ + contribs: 'wallpaperArtistContribs', + artwork: 'wallpaperArtwork', + value: input.value([]), + }), - additionalFiles: additionalFiles(), + wallpaperParts(), + ], - trackSections: thingList({ - class: input.value(TrackSection), - }), + bannerArtwork: [ + exitWithoutDependency({ + dependency: 'bannerArtistContribs', + mode: input.value('empty'), + value: input.value(null), + }), - artistContribs: contributionList({ - date: 'date', - artistProperty: input.value('albumArtistContributions'), - }), + constitutibleArtwork.fromYAMLFieldSpec + .call(this, 'Banner Artwork'), + ], - coverArtistContribs: [ + bannerArtistContribs: [ withCoverArtDate(), contributionList({ date: '#coverArtDate', - artistProperty: input.value('albumCoverArtistContributions'), + artistProperty: input.value('albumBannerArtistContributions'), }), ], - trackCoverArtistContribs: contributionList({ - // May be null, indicating cover art was added for tracks on the date - // each track specifies, or else the track's own release date. - date: 'trackArtDate', - - // This is the "correct" value, but it gets overwritten - with the same - // value - regardless. - artistProperty: input.value('trackCoverArtistContributions'), - }), + bannerFileExtension: [ + exitWithoutArtwork({ + contribs: 'bannerArtistContribs', + artwork: 'bannerArtwork', + }), - wallpaperArtistContribs: [ - withCoverArtDate(), + fileExtension('jpg'), + ], - contributionList({ - date: '#coverArtDate', - artistProperty: input.value('albumWallpaperArtistContributions'), + bannerDimensions: [ + exitWithoutArtwork({ + contribs: 'bannerArtistContribs', + artwork: 'bannerArtwork', }), - ], - bannerArtistContribs: [ - withCoverArtDate(), + dimensions(), + ], - contributionList({ - date: '#coverArtDate', - artistProperty: input.value('albumBannerArtistContributions'), + bannerStyle: [ + exitWithoutArtwork({ + contribs: 'bannerArtistContribs', + artwork: 'bannerArtwork', }), + + simpleString(), ], + // > Update & expose - Groups + groups: referenceList({ class: input.value(Group), find: soupyFind.input('group'), }), - artTags: [ - exitWithoutContribs({ - contribs: 'coverArtistContribs', - value: input.value([]), - }), + // > Update & expose - Content entries - referenceList({ - class: input.value(ArtTag), - find: soupyFind.input('artTag'), - }), - ], + commentary: thingList({ + class: input.value(CommentaryEntry), + }), - referencedArtworks: [ - exitWithoutContribs({ - contribs: 'coverArtistContribs', - value: input.value([]), - }), + creditingSources: thingList({ + class: input.value(CreditingSourcesEntry), + }), - referencedArtworkList(), - ], + // > Update & expose - Additional files - // Update only + additionalFiles: thingList({ + class: input.value(AdditionalFile), + }), + + // > Update only find: soupyFind(), reverse: soupyReverse(), @@ -305,13 +426,23 @@ export class Album extends Thing { class: input.value(WikiInfo), }), - // Expose only + // > Expose only + + isAlbum: [ + exposeConstant({ + value: input.value(true), + }), + ], commentatorArtists: commentatorArtists(), hasCoverArt: [ - withHasCoverArt(), - exposeDependency({dependency: '#hasCoverArt'}), + withHasArtwork({ + contribs: 'coverArtistContribs', + artworks: 'coverArtworks', + }), + + exposeDependency({dependency: '#hasArtwork'}), ], hasWallpaperArt: contribsPresent({contribs: 'wallpaperArtistContribs'}), @@ -379,6 +510,20 @@ export class Album extends Thing { : [album.name]), }, + albumSinglesOnly: { + referencing: ['album'], + + bindTo: 'albumData', + + incldue: album => + album.style === 'single', + + getMatchableNames: album => + (album.alwaysReferenceByDirectory + ? [] + : [album.name]), + }, + albumWithArtwork: { referenceTypes: [ 'album', @@ -392,8 +537,8 @@ export class Album extends Thing { album.hasCoverArt, getMatchableNames: album => - (album.alwaysReferenceByDirectory - ? [] + (album.alwaysReferenceByDirectory + ? [] : [album.name]), }, @@ -455,6 +600,9 @@ export class Album extends Thing { albumArtistContributionsBy: soupyReverse.contributionsBy('albumData', 'artistContribs'), + albumTrackArtistContributionsBy: + soupyReverse.contributionsBy('albumData', 'trackArtistContribs'), + albumCoverArtistContributionsBy: soupyReverse.artworkContributionsBy('albumData', 'coverArtworks'), @@ -474,21 +622,15 @@ export class Album extends Thing { static [Thing.yamlDocumentSpec] = { fields: { - 'Album': {property: 'name'}, + // Identifying metadata + 'Album': {property: 'name'}, 'Directory': {property: 'directory'}, 'Directory Suffix': {property: 'directorySuffix'}, 'Suffix Track Directories': {property: 'suffixTrackDirectories'}, - 'Always Reference By Directory': {property: 'alwaysReferenceByDirectory'}, - 'Always Reference Tracks By Directory': { - property: 'alwaysReferenceTracksByDirectory', - }, - - 'Additional Names': { - property: 'additionalNames', - transform: parseAdditionalNames, - }, + 'Always Reference Tracks By Directory': {property: 'alwaysReferenceTracksByDirectory'}, + 'Style': {property: 'style'}, 'Bandcamp Album ID': { property: 'bandcampAlbumIdentifier', @@ -500,18 +642,61 @@ export class Album extends Thing { transform: String, }, + 'Additional Names': { + property: 'additionalNames', + transform: parseAdditionalNames, + }, + 'Date': { property: 'date', transform: parseDate, }, - 'Color': {property: 'color'}, - 'URLs': {property: 'urls'}, + 'Date Added': { + property: 'dateAddedToWiki', + transform: parseDate, + }, + + // Credits and contributors + + 'Artists': { + property: 'artistContribs', + transform: parseContributors, + }, + + 'Track Artist Text': { + property: 'trackArtistText', + }, + + 'Track Artists': { + property: 'trackArtistContribs', + transform: parseContributors, + }, + + // General configuration + + 'Count Tracks In Artist Totals': {property: 'countTracksInArtistTotals'}, + + 'Show Album In Tracks Without Artists': { + property: 'showAlbumInTracksWithoutArtists', + }, 'Has Track Numbers': {property: 'hasTrackNumbers'}, 'Listed on Homepage': {property: 'isListedOnHomepage'}, 'Listed in Galleries': {property: 'isListedInGalleries'}, + 'Hide Duration': {property: 'hideDuration'}, + + // General metadata + + 'Color': {property: 'color'}, + + 'URLs': {property: 'urls'}, + + // Artworks + // (Note - this YAML section is deliberately ordered differently + // than the corresponding property descriptors.) + 'Cover Artwork': { property: 'coverArtworks', transform: @@ -555,27 +740,29 @@ export class Album extends Thing { }), }, + 'Cover Artists': { + property: 'coverArtistContribs', + transform: parseContributors, + }, + 'Cover Art Date': { property: 'coverArtDate', transform: parseDate, }, - 'Default Track Cover Art Date': { - property: 'trackArtDate', - transform: parseDate, + 'Cover Art Dimensions': { + property: 'coverArtDimensions', + transform: parseDimensions, }, - 'Date Added': { - property: 'dateAddedToWiki', - transform: parseDate, + 'Default Track Cover Artists': { + property: 'trackCoverArtistContribs', + transform: parseContributors, }, - 'Cover Art File Extension': {property: 'coverArtFileExtension'}, - 'Track Art File Extension': {property: 'trackCoverArtFileExtension'}, - - 'Cover Art Dimensions': { - property: 'coverArtDimensions', - transform: parseDimensions, + 'Default Track Cover Art Date': { + property: 'trackArtDate', + transform: parseDate, }, 'Default Track Dimensions': { @@ -589,7 +776,6 @@ export class Album extends Thing { }, 'Wallpaper Style': {property: 'wallpaperStyle'}, - 'Wallpaper File Extension': {property: 'wallpaperFileExtension'}, 'Wallpaper Parts': { property: 'wallpaperParts', @@ -601,58 +787,70 @@ export class Album extends Thing { transform: parseContributors, }, - 'Banner Style': {property: 'bannerStyle'}, - 'Banner File Extension': {property: 'bannerFileExtension'}, - 'Banner Dimensions': { property: 'bannerDimensions', transform: parseDimensions, }, - 'Commentary': { - property: 'commentary', - transform: parseCommentary, - }, + 'Banner Style': {property: 'bannerStyle'}, - 'Credit Sources': { - property: 'creditSources', - transform: parseCreditingSources, - }, + 'Cover Art File Extension': {property: 'coverArtFileExtension'}, + 'Track Art File Extension': {property: 'trackCoverArtFileExtension'}, + 'Wallpaper File Extension': {property: 'wallpaperFileExtension'}, + 'Banner File Extension': {property: 'bannerFileExtension'}, - 'Additional Files': { - property: 'additionalFiles', - transform: parseAdditionalFiles, - }, + 'Art Tags': {property: 'artTags'}, 'Referenced Artworks': { property: 'referencedArtworks', transform: parseAnnotatedReferences, }, - 'Franchises': {ignore: true}, + // Groups - 'Artists': { - property: 'artistContribs', - transform: parseContributors, + 'Groups': {property: 'groups'}, + + // Content entries + + 'Commentary': { + property: 'commentary', + transform: parseCommentary, }, - 'Cover Artists': { - property: 'coverArtistContribs', - transform: parseContributors, + 'Crediting Sources': { + property: 'creditingSources', + transform: parseCreditingSources, }, - 'Default Track Cover Artists': { - property: 'trackCoverArtistContribs', - transform: parseContributors, + // Additional files + + 'Additional Files': { + property: 'additionalFiles', + transform: parseAdditionalFiles, }, - 'Groups': {property: 'groups'}, - 'Art Tags': {property: 'artTags'}, + // Shenanigans + 'Franchises': {ignore: true}, 'Review Points': {ignore: true}, }, invalidFieldCombinations: [ + {message: `Move commentary on singles to the track`, fields: [ + ['Style', 'single'], + 'Commentary', + ]}, + + {message: `Move crediting sources on singles to the track`, fields: [ + ['Style', 'single'], + 'Crediting Sources', + ]}, + + {message: `Move additional names on singles to the track`, fields: [ + ['Style', 'single'], + 'Additional Names', + ]}, + {message: `Specify one wallpaper style or multiple wallpaper parts, not both`, fields: [ 'Wallpaper Parts', 'Wallpaper Style', @@ -692,6 +890,7 @@ export class Album extends Thing { const artworkData = []; const commentaryData = []; const creditingSourceData = []; + const referencingSourceData = []; const lyricsData = []; for (const {header: album, entries} of results) { @@ -705,8 +904,6 @@ export class Album extends Thing { isDefaultTrackSection: true, }); - const albumRef = Thing.getReference(album); - const closeCurrentTrackSection = () => { if ( currentTrackSection.isDefaultTrackSection && @@ -740,7 +937,8 @@ export class Album extends Thing { artworkData.push(...entry.trackArtworks); commentaryData.push(...entry.commentary); - creditingSourceData.push(...entry.creditSources); + creditingSourceData.push(...entry.creditingSources); + referencingSourceData.push(...entry.referencingSources); // TODO: As exposed, Track.lyrics tries to inherit from the main // release, which is impossible before the data's been linked. @@ -763,7 +961,7 @@ export class Album extends Thing { } commentaryData.push(...album.commentary); - creditingSourceData.push(...album.creditSources); + creditingSourceData.push(...album.creditingSources); album.trackSections = trackSections; } @@ -776,6 +974,7 @@ export class Album extends Thing { artworkData, commentaryData, creditingSourceData, + referencingSourceData, lyricsData, }; }, @@ -786,6 +985,14 @@ export class Album extends Thing { }, }); + getOwnAdditionalFilePath(_file, filename) { + return [ + 'media.albumAdditionalFile', + this.directory, + filename, + ]; + } + getOwnArtworkPath(artwork) { if (artwork === this.bannerArtwork) { return [ @@ -823,19 +1030,55 @@ export class Album extends Thing { artwork.fileExtension, ]; } + + // As of writing, albums don't even have a `duration` property... + // so this function will never be called... but the message stands... + countOwnContributionInDurationTotals(_contrib) { + return false; + } } export class TrackSection extends Thing { static [Thing.friendlyName] = `Track Section`; static [Thing.referenceType] = `track-section`; - static [Thing.getPropertyDescriptors] = ({Album, Track}) => ({ + static [Thing.getPropertyDescriptors] = ({Track}) => ({ // Update & expose name: name('Unnamed Track Section'), unqualifiedDirectory: directory(), + directorySuffix: [ + exposeUpdateValueOrContinue({ + validate: input.value(isDirectory), + }), + + withAlbum(), + + withPropertyFromObject({ + object: '#album', + property: input.value('directorySuffix'), + }), + + exposeDependency({dependency: '#album.directorySuffix'}), + ], + + suffixTrackDirectories: [ + exposeUpdateValueOrContinue({ + validate: input.value(isBoolean), + }), + + withAlbum(), + + withPropertyFromObject({ + object: '#album', + property: input.value('suffixTrackDirectories'), + }), + + exposeDependency({dependency: '#album.suffixTrackDirectories'}), + ], + color: [ exposeUpdateValueOrContinue({ validate: input.value(isColor), @@ -861,6 +1104,21 @@ export class TrackSection extends Thing { dateOriginallyReleased: simpleDate(), + countTracksInArtistTotals: [ + exposeUpdateValueOrContinue({ + validate: input.value(isBoolean), + }), + + withAlbum(), + + withPropertyFromObject({ + object: '#album', + property: input.value('countTracksInArtistTotals'), + }), + + exposeDependency({dependency: '#album.countTracksInArtistTotals'}), + ], + isDefaultTrackSection: flag(false), description: contentString(), @@ -880,6 +1138,12 @@ export class TrackSection extends Thing { // Expose only + isTrackSection: [ + exposeConstant({ + value: input.value(true), + }), + ], + directory: [ withAlbum(), @@ -941,6 +1205,9 @@ export class TrackSection extends Thing { static [Thing.yamlDocumentSpec] = { fields: { 'Section': {property: 'name'}, + 'Directory Suffix': {property: 'directorySuffix'}, + 'Suffix Track Directories': {property: 'suffixTrackDirectories'}, + 'Color': {property: 'color'}, 'Start Counting From': {property: 'startCountingFrom'}, @@ -949,6 +1216,8 @@ export class TrackSection extends Thing { transform: parseDate, }, + 'Count Tracks In Artist Totals': {property: 'countTracksInArtistTotals'}, + 'Description': {property: 'description'}, }, }; @@ -958,11 +1227,13 @@ export class TrackSection extends Thing { parts.push(Thing.prototype[inspect.custom].apply(this)); - if (depth >= 0) { + if (depth >= 0) showAlbum: { let album = null; try { album = this.album; - } catch {} + } catch { + break showAlbum; + } let first = null; try { @@ -974,22 +1245,20 @@ export class TrackSection extends Thing { last = this.tracks.at(-1).trackNumber; } catch {} - if (album) { - const albumName = album.name; - const albumIndex = album.trackSections.indexOf(this); + const albumName = album.name; + const albumIndex = album.trackSections.indexOf(this); - const num = - (albumIndex === -1 - ? 'indeterminate position' - : `#${albumIndex + 1}`); + const num = + (albumIndex === -1 + ? 'indeterminate position' + : `#${albumIndex + 1}`); - const range = - (albumIndex >= 0 && first !== null && last !== null - ? `: ${first}-${last}` - : ''); + const range = + (albumIndex >= 0 && first !== null && last !== null + ? `: ${first}-${last}` + : ''); - parts.push(` (${colors.yellow(num + range)} in ${colors.green(albumName)})`); - } + parts.push(` (${colors.yellow(num + range)} in ${colors.green(`"${albumName}"`)})`); } return parts.join(''); diff --git a/src/data/things/art-tag.js b/src/data/things/art-tag.js index 57e156ee..fff724cb 100644 --- a/src/data/things/art-tag.js +++ b/src/data/things/art-tag.js @@ -1,18 +1,25 @@ +export const DATA_ART_TAGS_DIRECTORY = 'art-tags'; export const ART_TAG_DATA_FILE = 'tags.yaml'; +import {readFile} from 'node:fs/promises'; +import * as path from 'node:path'; + import {input} from '#composite'; -import find from '#find'; -import {sortAlphabetically, sortAlbumsTracksChronologically} from '#sort'; +import {traverse} from '#node-utils'; +import {sortAlphabetically} from '#sort'; import Thing from '#thing'; import {unique} from '#sugar'; import {isName} from '#validators'; import {parseAdditionalNames, parseAnnotatedReferences} from '#yaml'; -import {exitWithoutDependency, exposeDependency, exposeUpdateValueOrContinue} - from '#composite/control-flow'; +import { + exitWithoutDependency, + exposeConstant, + exposeDependency, + exposeUpdateValueOrContinue, +} from '#composite/control-flow'; import { - additionalNameList, annotatedReferenceList, color, contentString, @@ -23,8 +30,8 @@ import { name, soupyFind, soupyReverse, + thingList, urls, - wikiData, } from '#composite/wiki-properties'; import {withAllDescendantArtTags, withAncestorArtTagBaobabTree} @@ -34,7 +41,7 @@ export class ArtTag extends Thing { static [Thing.referenceType] = 'tag'; static [Thing.friendlyName] = `Art Tag`; - static [Thing.getPropertyDescriptors] = ({Album, Track}) => ({ + static [Thing.getPropertyDescriptors] = ({AdditionalName}) => ({ // Update & expose name: name('Unnamed Art Tag'), @@ -55,7 +62,9 @@ export class ArtTag extends Thing { }, ], - additionalNames: additionalNameList(), + additionalNames: thingList({ + class: input.value(AdditionalName), + }), description: contentString(), @@ -79,6 +88,12 @@ export class ArtTag extends Thing { // Expose only + isArtTag: [ + exposeConstant({ + value: input.value(true), + }), + ], + descriptionShort: [ exitWithoutDependency({ dependency: 'description', @@ -174,13 +189,25 @@ export class ArtTag extends Thing { }; static [Thing.getYamlLoadingSpec] = ({ - documentModes: {allInOne}, + documentModes: {allTogether}, thingConstructors: {ArtTag}, }) => ({ title: `Process art tags file`, - file: ART_TAG_DATA_FILE, - documentMode: allInOne, + files: dataPath => + Promise.allSettled([ + readFile(path.join(dataPath, ART_TAG_DATA_FILE)) + .then(() => [ART_TAG_DATA_FILE]), + + traverse(path.join(dataPath, DATA_ART_TAGS_DIRECTORY), { + filterFile: name => path.extname(name) === '.yaml', + prefixPath: DATA_ART_TAGS_DIRECTORY, + }), + ]).then(results => results + .filter(({status}) => status === 'fulfilled') + .flatMap(({value}) => value)), + + documentMode: allTogether, documentThing: ArtTag, save: (results) => ({artTagData: results}), diff --git a/src/data/things/artist.js b/src/data/things/artist.js index 9e329c74..2905d893 100644 --- a/src/data/things/artist.js +++ b/src/data/things/artist.js @@ -5,14 +5,21 @@ import {inspect} from 'node:util'; import CacheableObject from '#cacheable-object'; import {colors} from '#cli'; import {input} from '#composite'; -import {sortAlphabetically} from '#sort'; import {stitchArrays} from '#sugar'; import Thing from '#thing'; import {isName, validateArrayItems} from '#validators'; import {getKebabCase} from '#wiki-data'; import {parseArtwork} from '#yaml'; -import {exitWithoutDependency} from '#composite/control-flow'; +import { + sortAlbumsTracksChronologically, + sortArtworksChronologically, + sortAlphabetically, + sortContributionsChronologically, +} from '#sort'; + +import {exitWithoutDependency, exposeConstant} from '#composite/control-flow'; +import {withReverseReferenceList} from '#composite/wiki-data'; import { constitutibleArtwork, @@ -26,7 +33,6 @@ import { soupyFind, soupyReverse, urls, - wikiData, } from '#composite/wiki-properties'; import {artistTotalDuration} from '#composite/things/artist'; @@ -35,7 +41,7 @@ export class Artist extends Thing { static [Thing.referenceType] = 'artist'; static [Thing.wikiDataArray] = 'artistData'; - static [Thing.getPropertyDescriptors] = ({Album, Flash, Group, Track}) => ({ + static [Thing.getPropertyDescriptors] = () => ({ // Update & expose name: name('Unnamed Artist'), @@ -77,6 +83,12 @@ export class Artist extends Thing { // Expose only + isArtist: [ + exposeConstant({ + value: input.value(true), + }), + ], + trackArtistContributions: reverseReferenceList({ reverse: soupyReverse.input('trackArtistContributionsBy'), }), @@ -97,6 +109,10 @@ export class Artist extends Thing { reverse: soupyReverse.input('albumArtistContributionsBy'), }), + albumTrackArtistContributions: reverseReferenceList({ + reverse: soupyReverse.input('albumTrackArtistContributionsBy'), + }), + albumCoverArtistContributions: reverseReferenceList({ reverse: soupyReverse.input('albumCoverArtistContributionsBy'), }), @@ -125,6 +141,102 @@ export class Artist extends Thing { reverse: soupyReverse.input('groupsCloselyLinkedTo'), }), + musicContributions: [ + withReverseReferenceList({ + reverse: soupyReverse.input('trackArtistContributionsBy'), + }).outputs({ + '#reverseReferenceList': '#trackArtistContribs', + }), + + withReverseReferenceList({ + reverse: soupyReverse.input('trackContributorContributionsBy'), + }).outputs({ + '#reverseReferenceList': '#trackContributorContribs', + }), + + { + dependencies: [ + '#trackArtistContribs', + '#trackContributorContribs', + ], + + compute: (continuation, { + ['#trackArtistContribs']: trackArtistContribs, + ['#trackContributorContribs']: trackContributorContribs, + }) => continuation({ + ['#contributions']: [ + ...trackArtistContribs, + ...trackContributorContribs, + ], + }), + }, + + { + dependencies: ['#contributions'], + compute: ({'#contributions': contributions}) => + sortContributionsChronologically( + contributions, + sortAlbumsTracksChronologically), + }, + ], + + artworkContributions: [ + withReverseReferenceList({ + reverse: soupyReverse.input('trackCoverArtistContributionsBy'), + }).outputs({ + '#reverseReferenceList': '#trackCoverArtistContribs', + }), + + withReverseReferenceList({ + reverse: soupyReverse.input('albumCoverArtistContributionsBy'), + }).outputs({ + '#reverseReferenceList': '#albumCoverArtistContribs', + }), + + withReverseReferenceList({ + reverse: soupyReverse.input('albumWallpaperArtistContributionsBy'), + }).outputs({ + '#reverseReferenceList': '#albumWallpaperArtistContribs', + }), + + withReverseReferenceList({ + reverse: soupyReverse.input('albumBannerArtistContributionsBy'), + }).outputs({ + '#reverseReferenceList': '#albumBannerArtistContribs', + }), + + { + dependencies: [ + '#trackCoverArtistContribs', + '#albumCoverArtistContribs', + '#albumWallpaperArtistContribs', + '#albumBannerArtistContribs', + ], + + compute: (continuation, { + ['#trackCoverArtistContribs']: trackCoverArtistContribs, + ['#albumCoverArtistContribs']: albumCoverArtistContribs, + ['#albumWallpaperArtistContribs']: albumWallpaperArtistContribs, + ['#albumBannerArtistContribs']: albumBannerArtistContribs, + }) => continuation({ + ['#contributions']: [ + ...trackCoverArtistContribs, + ...albumCoverArtistContribs, + ...albumWallpaperArtistContribs, + ...albumBannerArtistContribs, + ], + }), + }, + + { + dependencies: ['#contributions'], + compute: ({'#contributions': contributions}) => + sortContributionsChronologically( + contributions, + sortArtworksChronologically), + }, + ], + totalDuration: artistTotalDuration(), }); @@ -287,7 +399,7 @@ export class Artist extends Thing { let aliasedArtist; try { aliasedArtist = this.aliasedArtist.name; - } catch (_error) { + } catch { aliasedArtist = CacheableObject.getUpdateValue(this, 'aliasedArtist'); } diff --git a/src/data/things/artwork.js b/src/data/things/artwork.js index 3cdb07d0..916aac0a 100644 --- a/src/data/things/artwork.js +++ b/src/data/things/artwork.js @@ -1,5 +1,6 @@ import {inspect} from 'node:util'; +import {colors} from '#cli'; import {input} from '#composite'; import find from '#find'; import Thing from '#thing'; @@ -24,7 +25,7 @@ import { parseDimensions, } from '#yaml'; -import {withIndexInList, withPropertyFromObject} from '#composite/data'; +import {withPropertyFromList, withPropertyFromObject} from '#composite/data'; import { exitWithoutDependency, @@ -38,7 +39,6 @@ import { withRecontextualizedContributionList, withResolvedAnnotatedReferenceList, withResolvedContribs, - withResolvedReferenceList, } from '#composite/wiki-data'; import { @@ -54,20 +54,18 @@ import { } from '#composite/wiki-properties'; import { + withArtTags, withAttachedArtwork, withContainingArtworkList, + withContentWarningArtTags, withContribsFromAttachedArtwork, - withPropertyFromAttachedArtwork, withDate, } from '#composite/things/artwork'; export class Artwork extends Thing { static [Thing.referenceType] = 'artwork'; - static [Thing.getPropertyDescriptors] = ({ - ArtTag, - Contribution, - }) => ({ + static [Thing.getPropertyDescriptors] = ({ArtTag}) => ({ // Update & expose unqualifiedDirectory: directory({ @@ -79,6 +77,8 @@ export class Artwork extends Thing { label: simpleString(), source: contentString(), + originDetails: contentString(), + showFilename: simpleString(), dateFromThingProperty: simpleString(), @@ -135,7 +135,7 @@ export class Artwork extends Thing { }), exitWithoutDependency({ - dependency: 'artistContribsFromThingProperty', + dependency: 'dimensionsFromThingProperty', value: input.value(null), }), @@ -171,6 +171,7 @@ export class Artwork extends Thing { withResolvedContribs({ from: input.updateValue({validate: isContributionList}), date: '#date', + thingProperty: input.thisProperty(), artistProperty: 'artistContribsArtistProperty', }), @@ -206,50 +207,21 @@ export class Artwork extends Thing { }), ], + style: simpleString(), + artTagsFromThingProperty: simpleString(), artTags: [ - withResolvedReferenceList({ - list: input.updateValue({ + withArtTags({ + from: input.updateValue({ validate: validateReferenceList(ArtTag[Thing.referenceType]), }), - - find: soupyFind.input('artTag'), - }), - - exposeDependencyOrContinue({ - dependency: '#resolvedReferenceList', - mode: input.value('empty'), - }), - - withPropertyFromAttachedArtwork({ - property: input.value('artTags'), - }), - - exposeDependencyOrContinue({ - dependency: '#attachedArtwork.artTags', - }), - - exitWithoutDependency({ - dependency: 'artTagsFromThingProperty', - value: input.value([]), - }), - - withPropertyFromObject({ - object: 'thing', - property: 'artTagsFromThingProperty', - }).outputs({ - ['#value']: '#artTags', }), - exposeDependencyOrContinue({ + exposeDependency({ dependency: '#artTags', }), - - exposeConstant({ - value: input.value([]), - }), ], referencedArtworksFromThingProperty: simpleString(), @@ -323,6 +295,12 @@ export class Artwork extends Thing { // Expose only + isArtwork: [ + exposeConstant({ + value: input.value(true), + }), + ], + referencedByArtworks: reverseReferenceList({ reverse: soupyReverse.input('artworksWhichReference'), }), @@ -371,6 +349,42 @@ export class Artwork extends Thing { attachingArtworks: reverseReferenceList({ reverse: soupyReverse.input('artworksWhichAttach'), }), + + groups: [ + withPropertyFromObject({ + object: 'thing', + property: input.value('groups'), + }), + + exposeDependencyOrContinue({ + dependency: '#thing.groups', + }), + + exposeConstant({ + value: input.value([]), + }), + ], + + contentWarningArtTags: [ + withContentWarningArtTags(), + + exposeDependency({ + dependency: '#contentWarningArtTags', + }), + ], + + contentWarnings: [ + withContentWarningArtTags(), + + withPropertyFromList({ + list: '#contentWarningArtTags', + property: input.value('name'), + }), + + exposeDependency({ + dependency: '#contentWarningArtTags.name', + }), + ], }); static [Thing.yamlDocumentSpec] = { @@ -385,6 +399,8 @@ export class Artwork extends Thing { 'Label': {property: 'label'}, 'Source': {property: 'source'}, + 'Origin Details': {property: 'originDetails'}, + 'Show Filename': {property: 'showFilename'}, 'Date': { property: 'date', @@ -398,6 +414,8 @@ export class Artwork extends Thing { transform: parseContributors, }, + 'Style': {property: 'style'}, + 'Tags': {property: 'artTags'}, 'Referenced Artworks': { @@ -456,6 +474,18 @@ export class Artwork extends Thing { return this.thing.getOwnArtworkPath(this); } + countOwnContributionInContributionTotals(contrib) { + if (this.attachAbove) { + return false; + } + + if (contrib.annotation?.startsWith('edits for wiki')) { + return false; + } + + return true; + } + [inspect.custom](depth, options, inspect) { const parts = []; diff --git a/src/data/things/content.js b/src/data/things/content.js index 7f352795..a3dfc183 100644 --- a/src/data/things/content.js +++ b/src/data/things/content.js @@ -1,31 +1,37 @@ import {input} from '#composite'; -import find from '#find'; import Thing from '#thing'; import {is, isDate} from '#validators'; import {parseDate} from '#yaml'; -import {contentString, referenceList, simpleDate, soupyFind, thing} +import {contentString, simpleDate, soupyFind, thing} from '#composite/wiki-properties'; import { + exitWithoutDependency, exposeConstant, + exposeDependency, exposeDependencyOrContinue, exposeUpdateValueOrContinue, withResultOfAvailabilityCheck, } from '#composite/control-flow'; -import {withWebArchiveDate} from '#composite/things/commentary-entry'; +import { + contentArtists, + hasAnnotationPart, + withAnnotationParts, + withHasAnnotationPart, + withSourceText, + withSourceURLs, + withWebArchiveDate, +} from '#composite/things/content'; export class ContentEntry extends Thing { - static [Thing.getPropertyDescriptors] = ({Artist}) => ({ + static [Thing.getPropertyDescriptors] = () => ({ // Update & expose thing: thing(), - artists: referenceList({ - class: input.value(Artist), - find: soupyFind.input('artist'), - }), + artists: contentArtists(), artistText: contentString(), @@ -44,6 +50,10 @@ export class ContentEntry extends Thing { }, accessKind: [ + exitWithoutDependency({ + dependency: 'accessDate', + }), + exposeUpdateValueOrContinue({ validate: input.value( is(...[ @@ -67,7 +77,7 @@ export class ContentEntry extends Thing { }, exposeConstant({ - value: input.value(null), + value: input.value('accessed'), }), ], @@ -96,6 +106,32 @@ export class ContentEntry extends Thing { // Update only find: soupyFind(), + + // Expose only + + isContentEntry: [ + exposeConstant({ + value: input.value(true), + }), + ], + + annotationParts: [ + withAnnotationParts({ + mode: input.value('strings'), + }), + + exposeDependency({dependency: '#annotationParts'}), + ], + + sourceText: [ + withSourceText(), + exposeDependency({dependency: '#sourceText'}), + ], + + sourceURLs: [ + withSourceURLs(), + exposeDependency({dependency: '#sourceURLs'}), + ], }); static [Thing.yamlDocumentSpec] = { @@ -117,6 +153,95 @@ export class ContentEntry extends Thing { }; } -export class CommentaryEntry extends ContentEntry {} -export class LyricsEntry extends ContentEntry {} -export class CreditingSourcesEntry extends ContentEntry {} +export class CommentaryEntry extends ContentEntry { + static [Thing.getPropertyDescriptors] = () => ({ + // Expose only + + isCommentaryEntry: [ + exposeConstant({ + value: input.value(true), + }), + ], + + isWikiEditorCommentary: hasAnnotationPart({ + part: input.value('wiki editor'), + }), + }); +} + +export class LyricsEntry extends ContentEntry { + static [Thing.getPropertyDescriptors] = () => ({ + // Update & expose + + originDetails: contentString(), + + // Expose only + + isLyricsEntry: [ + exposeConstant({ + value: input.value(true), + }), + ], + + isWikiLyrics: hasAnnotationPart({ + part: input.value('wiki lyrics'), + }), + + helpNeeded: hasAnnotationPart({ + part: input.value('help needed'), + }), + + hasSquareBracketAnnotations: [ + withHasAnnotationPart({ + part: input.value('wiki lyrics'), + }), + + exitWithoutDependency({ + dependency: '#hasAnnotationPart', + mode: input.value('falsy'), + value: input.value(false), + }), + + exitWithoutDependency({ + dependency: 'body', + value: input.value(false), + }), + + { + dependencies: ['body'], + compute: ({body}) => + /\[.*\]/m.test(body), + }, + ], + }); + + static [Thing.yamlDocumentSpec] = Thing.extendDocumentSpec(ContentEntry, { + fields: { + 'Origin Details': {property: 'originDetails'}, + }, + }); +} + +export class CreditingSourcesEntry extends ContentEntry { + static [Thing.getPropertyDescriptors] = () => ({ + // Expose only + + isCreditingSourcesEntry: [ + exposeConstant({ + value: input.value(true), + }), + ], + }); +} + +export class ReferencingSourcesEntry extends ContentEntry { + static [Thing.getPropertyDescriptors] = () => ({ + // Expose only + + isReferencingSourceEntry: [ + exposeConstant({ + value: input.value(true), + }), + ], + }); +} diff --git a/src/data/things/contribution.js b/src/data/things/contribution.js index c92fafb4..e1e248cb 100644 --- a/src/data/things/contribution.js +++ b/src/data/things/contribution.js @@ -5,10 +5,18 @@ import {colors} from '#cli'; import {input} from '#composite'; import {empty} from '#sugar'; import Thing from '#thing'; -import {isStringNonEmpty, isThing, validateReference} from '#validators'; +import {isBoolean, isStringNonEmpty, isThing, validateReference} + from '#validators'; -import {exitWithoutDependency, exposeDependency} from '#composite/control-flow'; -import {flag, simpleDate, soupyFind} from '#composite/wiki-properties'; +import {simpleDate, soupyFind} from '#composite/wiki-properties'; + +import { + exitWithoutDependency, + exposeConstant, + exposeDependency, + exposeDependencyOrContinue, + exposeUpdateValueOrContinue, +} from '#composite/control-flow'; import { withFilteredList, @@ -19,8 +27,6 @@ import { import { inheritFromContributionPresets, - thingPropertyMatches, - thingReferenceTypeMatches, withContainingReverseContributionList, withContributionArtist, withContributionContext, @@ -70,7 +76,26 @@ export class Contribution extends Thing { property: input.thisProperty(), }), - flag(true), + exposeUpdateValueOrContinue({ + validate: input.value(isBoolean), + }), + + { + dependencies: ['thing', input.myself()], + compute: (continuation, { + ['thing']: thing, + [input.myself()]: contribution, + }) => + (thing.countOwnContributionInContributionTotals?.(contribution) + ? true + : thing.countOwnContributionInContributionTotals + ? false + : continuation()), + }, + + exposeConstant({ + value: input.value(true), + }), ], countInDurationTotals: [ @@ -78,7 +103,37 @@ export class Contribution extends Thing { property: input.thisProperty(), }), - flag(true), + exposeUpdateValueOrContinue({ + validate: input.value(isBoolean), + }), + + withPropertyFromObject({ + object: 'thing', + property: input.value('duration'), + }), + + exitWithoutDependency({ + dependency: '#thing.duration', + mode: input.value('falsy'), + value: input.value(false), + }), + + { + dependencies: ['thing', input.myself()], + compute: (continuation, { + ['thing']: thing, + [input.myself()]: contribution, + }) => + (thing.countOwnContributionInDurationTotals?.(contribution) + ? true + : thing.countOwnContributionInDurationTotals + ? false + : continuation()), + }, + + exposeConstant({ + value: input.value(true), + }), ], // Update only @@ -87,6 +142,12 @@ export class Contribution extends Thing { // Expose only + isContribution: [ + exposeConstant({ + value: input.value(true), + }), + ], + context: [ withContributionContext(), @@ -167,38 +228,6 @@ export class Contribution extends Thing { }), ], - isArtistContribution: thingPropertyMatches({ - value: input.value('artistContribs'), - }), - - isContributorContribution: thingPropertyMatches({ - value: input.value('contributorContribs'), - }), - - isCoverArtistContribution: thingPropertyMatches({ - value: input.value('coverArtistContribs'), - }), - - isBannerArtistContribution: thingPropertyMatches({ - value: input.value('bannerArtistContribs'), - }), - - isWallpaperArtistContribution: thingPropertyMatches({ - value: input.value('wallpaperArtistContribs'), - }), - - isForTrack: thingReferenceTypeMatches({ - value: input.value('track'), - }), - - isForAlbum: thingReferenceTypeMatches({ - value: input.value('album'), - }), - - isForFlash: thingReferenceTypeMatches({ - value: input.value('flash'), - }), - previousBySameArtist: [ withContainingReverseContributionList().outputs({ '#containingReverseContributionList': '#list', @@ -238,6 +267,21 @@ export class Contribution extends Thing { dependency: '#nearbyItem', }), ], + + groups: [ + withPropertyFromObject({ + object: 'thing', + property: input.value('groups'), + }), + + exposeDependencyOrContinue({ + dependency: '#thing.groups', + }), + + exposeConstant({ + value: input.value([]), + }), + ], }); [inspect.custom](depth, options, inspect) { @@ -259,7 +303,7 @@ export class Contribution extends Thing { let artist; try { artist = this.artist; - } catch (_error) { + } catch { // Computing artist might crash for any reason - don't distract from // other errors as a result of inspecting this contribution. } diff --git a/src/data/things/flash.js b/src/data/things/flash.js index a0bcb523..73b22746 100644 --- a/src/data/things/flash.js +++ b/src/data/things/flash.js @@ -27,7 +27,6 @@ import { } from '#composite/control-flow'; import { - additionalNameList, color, commentatorArtists, constitutibleArtwork, @@ -44,7 +43,6 @@ import { thing, thingList, urls, - wikiData, } from '#composite/wiki-properties'; import {withFlashAct} from '#composite/things/flash'; @@ -54,10 +52,10 @@ export class Flash extends Thing { static [Thing.referenceType] = 'flash'; static [Thing.getPropertyDescriptors] = ({ + AdditionalName, CommentaryEntry, CreditingSourcesEntry, Track, - FlashAct, WikiInfo, }) => ({ // Update & expose @@ -127,13 +125,15 @@ export class Flash extends Thing { urls: urls(), - additionalNames: additionalNameList(), + additionalNames: thingList({ + class: input.value(AdditionalName), + }), commentary: thingList({ class: input.value(CommentaryEntry), }), - creditSources: thingList({ + creditingSources: thingList({ class: input.value(CreditingSourcesEntry), }), @@ -149,6 +149,12 @@ export class Flash extends Thing { // Expose only + isFlash: [ + exposeConstant({ + value: input.value(true), + }), + ], + commentatorArtists: commentatorArtists(), act: [ @@ -255,8 +261,8 @@ export class Flash extends Thing { transform: parseCommentary, }, - 'Credit Sources': { - property: 'creditSources', + 'Crediting Sources': { + property: 'creditingSources', transform: parseCreditingSources, }, @@ -317,6 +323,12 @@ export class FlashAct extends Thing { // Expose only + isFlashAct: [ + exposeConstant({ + value: input.value(true), + }), + ], + side: [ withFlashSide(), exposeDependency({dependency: '#flashSide'}), @@ -372,6 +384,14 @@ export class FlashSide extends Thing { // Update only find: soupyFind(), + + // Expose only + + isFlashSide: [ + exposeConstant({ + value: input.value(true), + }), + ], }); static [Thing.yamlDocumentSpec] = { @@ -459,7 +479,7 @@ export class FlashSide extends Thing { const artworkData = flashData.map(flash => flash.coverArtwork); const commentaryData = flashData.flatMap(flash => flash.commentary); - const creditingSourceData = flashData.flatMap(flash => flash.creditSources); + const creditingSourceData = flashData.flatMap(flash => flash.creditingSources); return { flashData, diff --git a/src/data/things/group.js b/src/data/things/group.js index b40d15b4..0935dc93 100644 --- a/src/data/things/group.js +++ b/src/data/things/group.js @@ -1,31 +1,73 @@ export const GROUP_DATA_FILE = 'groups.yaml'; +import {inspect} from 'node:util'; + +import {colors} from '#cli'; import {input} from '#composite'; import Thing from '#thing'; +import {is, isBoolean} from '#validators'; import {parseAnnotatedReferences, parseSerieses} from '#yaml'; +import {withPropertyFromObject} from '#composite/data'; +import {withUniqueReferencingThing} from '#composite/wiki-data'; + +import { + exposeConstant, + exposeDependencyOrContinue, + exposeUpdateValueOrContinue, +} from '#composite/control-flow'; + import { annotatedReferenceList, color, contentString, directory, + flag, name, referenceList, - seriesList, soupyFind, + soupyReverse, + thing, + thingList, urls, - wikiData, } from '#composite/wiki-properties'; export class Group extends Thing { static [Thing.referenceType] = 'group'; - static [Thing.getPropertyDescriptors] = ({Album, Artist}) => ({ + static [Thing.getPropertyDescriptors] = ({Album, Artist, Series}) => ({ // Update & expose name: name('Unnamed Group'), directory: directory(), + excludeFromGalleryTabs: [ + exposeUpdateValueOrContinue({ + validate: input.value(isBoolean), + }), + + withUniqueReferencingThing({ + reverse: soupyReverse.input('groupCategoriesWhichInclude'), + }).outputs({ + '#uniqueReferencingThing': '#category', + }), + + withPropertyFromObject({ + object: '#category', + property: input.value('excludeGroupsFromGalleryTabs'), + }), + + exposeDependencyOrContinue({ + dependency: '#category.excludeGroupsFromGalleryTabs', + }), + + exposeConstant({ + value: input.value(false), + }), + ], + + divideAlbumsByStyle: flag(false), + description: contentString(), urls: urls(), @@ -43,17 +85,23 @@ export class Group extends Thing { find: soupyFind.input('album'), }), - serieses: seriesList({ - group: input.myself(), + serieses: thingList({ + class: input.value(Series), }), // Update only find: soupyFind(), - reverse: soupyFind(), + reverse: soupyReverse(), // Expose only + isGroup: [ + exposeConstant({ + value: input.value(true), + }), + ], + descriptionShort: { flags: {expose: true}, @@ -129,6 +177,10 @@ export class Group extends Thing { fields: { 'Group': {property: 'name'}, 'Directory': {property: 'directory'}, + + 'Exclude From Gallery Tabs': {property: 'excludeFromGalleryTabs'}, + 'Divide Albums By Style': {property: 'divideAlbumsByStyle'}, + 'Description': {property: 'description'}, 'URLs': {property: 'urls'}, @@ -192,8 +244,9 @@ export class Group extends Thing { const groupData = results.filter(x => x instanceof Group); const groupCategoryData = results.filter(x => x instanceof GroupCategory); + const seriesData = groupData.flatMap(group => group.serieses); - return {groupData, groupCategoryData}; + return {groupData, groupCategoryData, seriesData}; }, // Groups aren't sorted at all, always preserving the order in the data @@ -212,6 +265,8 @@ export class GroupCategory extends Thing { name: name('Unnamed Group Category'), directory: directory(), + excludeGroupsFromGalleryTabs: flag(false), + color: color(), groups: referenceList({ @@ -222,6 +277,14 @@ export class GroupCategory extends Thing { // Update only find: soupyFind(), + + // Expose only + + isGroupCategory: [ + exposeConstant({ + value: input.value(true), + }), + ], }); static [Thing.reverseSpecs] = { @@ -236,7 +299,82 @@ export class GroupCategory extends Thing { static [Thing.yamlDocumentSpec] = { fields: { 'Category': {property: 'name'}, + 'Color': {property: 'color'}, + + 'Exclude Groups From Gallery Tabs': { + property: 'excludeGroupsFromGalleryTabs', + }, + }, + }; +} + +export class Series extends Thing { + static [Thing.getPropertyDescriptors] = ({Album, Group}) => ({ + // Update & expose + + name: name('Unnamed Series'), + + showAlbumArtists: { + flags: {update: true, expose: true}, + update: { + validate: + is('all', 'differing', 'none'), + }, + }, + + description: contentString(), + + group: thing({ + class: input.value(Group), + }), + + albums: referenceList({ + class: input.value(Album), + find: soupyFind.input('album'), + }), + + // Update only + + find: soupyFind(), + }); + + static [Thing.yamlDocumentSpec] = { + fields: { + 'Name': {property: 'name'}, + + 'Description': {property: 'description'}, + + 'Show Album Artists': {property: 'showAlbumArtists'}, + + 'Albums': {property: 'albums'}, }, }; + + [inspect.custom](depth, options, inspect) { + const parts = []; + + parts.push(Thing.prototype[inspect.custom].apply(this)); + + if (depth >= 0) showGroup: { + let group = null; + try { + group = this.group; + } catch { + break showGroup; + } + + const groupName = group.name; + const groupIndex = group.serieses.indexOf(this); + + const num = + (groupIndex === -1 + ? 'indeterminate position' + : `#${groupIndex + 1}`); + + parts.push(` (${colors.yellow(num)} in ${colors.green(`"${groupName}"`)})`); + } + + return parts.join(''); + } } diff --git a/src/data/things/homepage-layout.js b/src/data/things/homepage-layout.js index 82bad2d3..2456ca95 100644 --- a/src/data/things/homepage-layout.js +++ b/src/data/things/homepage-layout.js @@ -17,7 +17,7 @@ import { validateReference, } from '#validators'; -import {exposeDependency} from '#composite/control-flow'; +import {exposeConstant, exposeDependency} from '#composite/control-flow'; import {withResolvedReference} from '#composite/wiki-data'; import { @@ -47,6 +47,14 @@ export class HomepageLayout extends Thing { sections: thingList({ class: input.value(HomepageLayoutSection), }), + + // Expose only + + isHomepageLayout: [ + exposeConstant({ + value: input.value(true), + }), + ], }); static [Thing.yamlDocumentSpec] = { @@ -63,7 +71,6 @@ export class HomepageLayout extends Thing { thingConstructors: { HomepageLayout, HomepageLayoutSection, - HomepageLayoutAlbumsRow, }, }) => ({ title: `Process homepage layout file`, @@ -157,6 +164,14 @@ export class HomepageLayoutSection extends Thing { rows: thingList({ class: input.value(HomepageLayoutRow), }), + + // Expose only + + isHomepageLayoutSection: [ + exposeConstant({ + value: input.value(true), + }), + ], }); static [Thing.yamlDocumentSpec] = { @@ -183,6 +198,12 @@ export class HomepageLayoutRow extends Thing { // Expose only + isHomepageLayoutRow: [ + exposeConstant({ + value: input.value(true), + }), + ], + type: { flags: {expose: true}, @@ -234,6 +255,12 @@ export class HomepageLayoutActionsRow extends HomepageLayoutRow { // Expose only + isHomepageLayoutActionsRow: [ + exposeConstant({ + value: input.value(true), + }), + ], + type: { flags: {expose: true}, expose: {compute: () => 'actions'}, @@ -250,7 +277,7 @@ export class HomepageLayoutActionsRow extends HomepageLayoutRow { export class HomepageLayoutAlbumCarouselRow extends HomepageLayoutRow { static [Thing.friendlyName] = `Homepage Album Carousel Row`; - static [Thing.getPropertyDescriptors] = (opts, {Album, Group} = opts) => ({ + static [Thing.getPropertyDescriptors] = (opts, {Album} = opts) => ({ ...HomepageLayoutRow[Thing.getPropertyDescriptors](opts), // Update & expose @@ -262,6 +289,12 @@ export class HomepageLayoutAlbumCarouselRow extends HomepageLayoutRow { // Expose only + isHomepageLayoutAlbumCarouselRow: [ + exposeConstant({ + value: input.value(true), + }), + ], + type: { flags: {expose: true}, expose: {compute: () => 'album carousel'}, @@ -322,6 +355,12 @@ export class HomepageLayoutAlbumGridRow extends HomepageLayoutRow { // Expose only + isHomepageLayoutAlbumGridRow: [ + exposeConstant({ + value: input.value(true), + }), + ], + type: { flags: {expose: true}, expose: {compute: () => 'album grid'}, diff --git a/src/data/things/index.js b/src/data/things/index.js index b832ab75..11307b50 100644 --- a/src/data/things/index.js +++ b/src/data/things/index.js @@ -9,6 +9,8 @@ import * as serialize from '#serialize'; import {withEntries} from '#sugar'; import Thing from '#thing'; +import * as additionalFileClasses from './additional-file.js'; +import * as additionalNameClasses from './additional-name.js'; import * as albumClasses from './album.js'; import * as artTagClasses from './art-tag.js'; import * as artistClasses from './artist.js'; @@ -26,6 +28,8 @@ import * as trackClasses from './track.js'; import * as wikiInfoClasses from './wiki-info.js'; const allClassLists = { + 'additional-file.js': additionalFileClasses, + 'additional-name.js': additionalNameClasses, 'album.js': albumClasses, 'art-tag.js': artTagClasses, 'artist.js': artistClasses, diff --git a/src/data/things/language.js b/src/data/things/language.js index a3f861bd..43f69f3d 100644 --- a/src/data/things/language.js +++ b/src/data/things/language.js @@ -1,24 +1,27 @@ -import { Temporal, toTemporalInstant } from '@js-temporal/polyfill'; +import {Temporal, toTemporalInstant} from '@js-temporal/polyfill'; import {withAggregate} from '#aggregate'; import CacheableObject from '#cacheable-object'; -import {logWarn} from '#cli'; +import {input} from '#composite'; import * as html from '#html'; -import {empty} from '#sugar'; +import {accumulateSum, empty, withEntries} from '#sugar'; import {isLanguageCode} from '#validators'; import Thing from '#thing'; +import {languageOptionRegex} from '#wiki-data'; import { + externalLinkSpec, getExternalLinkStringOfStyleFromDescriptors, getExternalLinkStringsFromDescriptors, isExternalLinkContext, - isExternalLinkSpec, isExternalLinkStyle, } from '#external-links'; +import {exitWithoutDependency, exposeConstant, exposeDependency} + from '#composite/control-flow'; import {externalFunction, flag, name} from '#composite/wiki-properties'; -export const languageOptionRegex = /{(?<name>[A-Z0-9_]+)}/g; +import {withStrings} from '#composite/things/language'; export class Language extends Thing { static [Thing.getPropertyDescriptors] = () => ({ @@ -60,52 +63,17 @@ export class Language extends Thing { // Mapping of translation keys to values (strings). Generally, don't // access this object directly - use methods instead. - strings: { - flags: {update: true, expose: true}, - update: {validate: (t) => typeof t === 'object'}, - - expose: { - dependencies: ['inheritedStrings', 'code'], - transform(strings, {inheritedStrings, code}) { - if (!strings && !inheritedStrings) return null; - if (!inheritedStrings) return strings; - - const validStrings = { - ...inheritedStrings, - ...strings, - }; - - const optionsFromTemplate = template => - Array.from(template.matchAll(languageOptionRegex)) - .map(({groups}) => groups.name); - - for (const [key, providedTemplate] of Object.entries(strings)) { - const inheritedTemplate = inheritedStrings[key]; - if (!inheritedTemplate) continue; - - const providedOptions = optionsFromTemplate(providedTemplate); - const inheritedOptions = optionsFromTemplate(inheritedTemplate); - - const missingOptionNames = - inheritedOptions.filter(name => !providedOptions.includes(name)); - - const misplacedOptionNames = - providedOptions.filter(name => !inheritedOptions.includes(name)); - - if (!empty(missingOptionNames) || !empty(misplacedOptionNames)) { - logWarn`Not using ${code ?? '(no code)'} string ${key}:`; - if (!empty(missingOptionNames)) - logWarn`- Missing options: ${missingOptionNames.join(', ')}`; - if (!empty(misplacedOptionNames)) - logWarn`- Unexpected options: ${misplacedOptionNames.join(', ')}`; - validStrings[key] = inheritedStrings[key]; - } - } - - return validStrings; - }, - }, - }, + strings: [ + withStrings({ + from: input.updateValue({ + validate: t => typeof t === 'object', + }), + }), + + exposeDependency({ + dependency: '#strings', + }), + ], // May be provided to specify "default" strings, generally (but not // necessarily) inherited from another Language object. @@ -114,19 +82,14 @@ export class Language extends Thing { update: {validate: (t) => typeof t === 'object'}, }, - // List of descriptors for providing to external link utilities when using - // language.formatExternalLink - refer to #external-links for info. - externalLinkSpec: { - flags: {update: true, expose: true}, - update: {validate: isExternalLinkSpec}, - }, - - // Update only - - escapeHTML: externalFunction(), - // Expose only + isLanguage: [ + exposeConstant({ + value: input.value(true), + }), + ], + onlyIfOptions: { flags: {expose: true}, expose: { @@ -135,12 +98,15 @@ export class Language extends Thing { }, intl_date: this.#intlHelper(Intl.DateTimeFormat, {full: true}), + intl_dateYear: this.#intlHelper(Intl.DateTimeFormat, {year: 'numeric'}), + intl_dateMonthDay: this.#intlHelper(Intl.DateTimeFormat, {month: 'numeric', day: 'numeric'}), intl_number: this.#intlHelper(Intl.NumberFormat), intl_listConjunction: this.#intlHelper(Intl.ListFormat, {type: 'conjunction'}), intl_listDisjunction: this.#intlHelper(Intl.ListFormat, {type: 'disjunction'}), intl_listUnit: this.#intlHelper(Intl.ListFormat, {type: 'unit'}), intl_pluralCardinal: this.#intlHelper(Intl.PluralRules, {type: 'cardinal'}), intl_pluralOrdinal: this.#intlHelper(Intl.PluralRules, {type: 'ordinal'}), + intl_wordSegmenter: this.#intlHelper(Intl.Segmenter, {granularity: 'word'}), validKeys: { flags: {expose: true}, @@ -158,19 +124,20 @@ export class Language extends Thing { }, // TODO: This currently isn't used. Is it still needed? - strings_htmlEscaped: { - flags: {expose: true}, - expose: { - dependencies: ['strings', 'inheritedStrings', 'escapeHTML'], - compute({strings, inheritedStrings, escapeHTML}) { - if (!(strings || inheritedStrings) || !escapeHTML) return null; - const allStrings = {...inheritedStrings, ...strings}; - return Object.fromEntries( - Object.entries(allStrings).map(([k, v]) => [k, escapeHTML(v)]) - ); - }, + strings_htmlEscaped: [ + withStrings(), + + exitWithoutDependency({ + dependency: '#strings', + }), + + { + dependencies: ['#strings'], + compute: ({'#strings': strings}) => + withEntries(strings, entries => entries + .map(([key, value]) => [key, html.escape(value)])), }, - }, + ], }); static #intlHelper (constructor, opts) { @@ -197,12 +164,25 @@ export class Language extends Thing { } } + countWords(text) { + this.assertIntlAvailable('intl_wordSegmenter'); + + const string = html.resolve(text, {normalize: 'plain'}); + const segments = this.intl_wordSegmenter.segment(string); + + return accumulateSum(segments, segment => segment.isWordLike ? 1 : 0); + } + getUnitForm(value) { this.assertIntlAvailable('intl_pluralCardinal'); return this.intl_pluralCardinal.select(value); } formatString(...args) { + if (typeof args.at(-1) === 'function') { + throw new Error(`Passed function - did you mean language.encapsulate() instead?`); + } + const hasOptions = typeof args.at(-1) === 'object' && args.at(-1) !== null; @@ -309,7 +289,7 @@ export class Language extends Thing { return undefined; } - return optionValue; + return this.sanitize(optionValue); }, }); @@ -374,26 +354,22 @@ export class Language extends Thing { partInProgress += template.slice(lastIndex, match.index); - // Sanitize string arguments in particular. These are taken to come from - // (raw) data and may include special characters that aren't meant to be - // rendered as HTML markup. - const sanitizedInsertion = - this.#sanitizeValueForInsertion(insertion); - - if (typeof sanitizedInsertion === 'string') { - // Join consecutive strings together. - partInProgress += sanitizedInsertion; - } else if ( - sanitizedInsertion instanceof html.Tag && - sanitizedInsertion.contentOnly - ) { - // Collapse string-only tag contents onto the current string part. - partInProgress += sanitizedInsertion.toString(); - } else { - // Push the string part in progress, then the insertion as-is. - outputParts.push(partInProgress); - outputParts.push(sanitizedInsertion); + const insertionItems = html.smush(insertion).content; + if (insertionItems.length === 1 && typeof insertionItems[0] !== 'string') { + // Push the insertion exactly as it is, rather than manipulating. + if (partInProgress) outputParts.push(partInProgress); + outputParts.push(insertion); partInProgress = ''; + } else for (const insertionItem of insertionItems) { + if (typeof insertionItem === 'string') { + // Join consecutive strings together. + partInProgress += insertionItem; + } else { + // Push the string part in progress, then the insertion as-is. + if (partInProgress) outputParts.push(partInProgress); + outputParts.push(insertionItem); + partInProgress = ''; + } } lastIndex = match.index + match[0].length; @@ -425,14 +401,9 @@ export class Language extends Thing { // html.Tag objects - gets left as-is, preserving the value exactly as it's // provided. #sanitizeValueForInsertion(value) { - const escapeHTML = CacheableObject.getUpdateValue(this, 'escapeHTML'); - if (!escapeHTML) { - throw new Error(`escapeHTML unavailable`); - } - switch (typeof value) { case 'string': - return escapeHTML(value); + return html.escape(value); case 'number': case 'boolean': @@ -488,22 +459,53 @@ export class Language extends Thing { // or both are undefined, that's just blank content. const hasStart = startDate !== null && startDate !== undefined; const hasEnd = endDate !== null && endDate !== undefined; - if (!hasStart || !hasEnd) { - if (startDate === endDate) { - return html.blank(); - } else if (hasStart) { - throw new Error(`Expected both start and end of date range, got only start`); - } else if (hasEnd) { - throw new Error(`Expected both start and end of date range, got only end`); - } else { - throw new Error(`Got mismatched ${startDate}/${endDate} for start and end`); - } + if (!hasStart && !hasEnd) { + return html.blank(); + } else if (hasStart && !hasEnd) { + throw new Error(`Expected both start and end of date range, got only start`); + } else if (!hasStart && hasEnd) { + throw new Error(`Expected both start and end of date range, got only end`); } this.assertIntlAvailable('intl_date'); return this.intl_date.formatRange(startDate, endDate); } + formatYear(date) { + if (date === null || date === undefined) { + return html.blank(); + } + + this.assertIntlAvailable('intl_dateYear'); + return this.intl_dateYear.format(date); + } + + formatMonthDay(date) { + if (date === null || date === undefined) { + return html.blank(); + } + + this.assertIntlAvailable('intl_dateMonthDay'); + return this.intl_dateMonthDay.format(date); + } + + formatYearRange(startDate, endDate) { + // formatYearRange expects both values to be present, but if both are null + // or both are undefined, that's just blank content. + const hasStart = startDate !== null && startDate !== undefined; + const hasEnd = endDate !== null && endDate !== undefined; + if (!hasStart && !hasEnd) { + return html.blank(); + } else if (hasStart && !hasEnd) { + throw new Error(`Expected both start and end of date range, got only start`); + } else if (!hasStart && hasEnd) { + throw new Error(`Expected both start and end of date range, got only end`); + } + + this.assertIntlAvailable('intl_dateYear'); + return this.intl_dateYear.formatRange(startDate, endDate); + } + formatDateDuration({ years: numYears = 0, months: numMonths = 0, @@ -665,10 +667,6 @@ export class Language extends Thing { style = 'platform', context = 'generic', } = {}) { - if (!this.externalLinkSpec) { - throw new TypeError(`externalLinkSpec unavailable`); - } - // Null or undefined url is blank content. if (url === null || url === undefined) { return html.blank(); @@ -677,7 +675,7 @@ export class Language extends Thing { isExternalLinkContext(context); if (style === 'all') { - return getExternalLinkStringsFromDescriptors(url, this.externalLinkSpec, { + return getExternalLinkStringsFromDescriptors(url, externalLinkSpec, { language: this, context, }); @@ -686,7 +684,7 @@ export class Language extends Thing { isExternalLinkStyle(style); const result = - getExternalLinkStringOfStyleFromDescriptors(url, style, this.externalLinkSpec, { + getExternalLinkStringOfStyleFromDescriptors(url, style, externalLinkSpec, { language: this, context, }); @@ -842,6 +840,18 @@ export class Language extends Thing { } } + typicallyLowerCase(string) { + // Utter nonsense implementation, so this only works on strings, + // not actual HTML content, and may rudely disrespect *intentful* + // capitalization of whatever goes into it. + + if (typeof string !== 'string') return string; + if (string.length <= 1) return string; + if (/^\S+?[A-Z]/.test(string)) return string; + + return string[0].toLowerCase() + string.slice(1); + } + // Utility function to quickly provide a useful string key // (generally a prefix) to stuff nested beneath it. encapsulate(...args) { @@ -900,7 +910,6 @@ Object.assign(Language.prototype, { countArtworks: countHelper('artworks'), countCommentaryEntries: countHelper('commentaryEntries', 'entries'), countContributions: countHelper('contributions'), - countCoverArts: countHelper('coverArts'), countDays: countHelper('days'), countFlashes: countHelper('flashes'), countMonths: countHelper('months'), diff --git a/src/data/things/news-entry.js b/src/data/things/news-entry.js index 43d1638e..28289f53 100644 --- a/src/data/things/news-entry.js +++ b/src/data/things/news-entry.js @@ -1,9 +1,11 @@ export const NEWS_DATA_FILE = 'news.yaml'; +import {input} from '#composite'; import {sortChronologically} from '#sort'; import Thing from '#thing'; import {parseDate} from '#yaml'; +import {exposeConstant} from '#composite/control-flow'; import {contentString, directory, name, simpleDate} from '#composite/wiki-properties'; @@ -22,6 +24,12 @@ export class NewsEntry extends Thing { // Expose only + isNewsEntry: [ + exposeConstant({ + value: input.value(true), + }), + ], + contentShort: { flags: {expose: true}, diff --git a/src/data/things/sorting-rule.js b/src/data/things/sorting-rule.js index b169a541..8ed3861a 100644 --- a/src/data/things/sorting-rule.js +++ b/src/data/things/sorting-rule.js @@ -22,6 +22,7 @@ import { reorderDocumentsInYAMLSourceText, } from '#yaml'; +import {exposeConstant} from '#composite/control-flow'; import {flag} from '#composite/wiki-properties'; function isSelectFollowingEntry(value) { @@ -47,6 +48,14 @@ export class SortingRule extends Thing { flags: {update: true, expose: true}, update: {validate: isStringNonEmpty}, }, + + // Expose only + + isSortingRule: [ + exposeConstant({ + value: input.value(true), + }), + ], }); static [Thing.yamlDocumentSpec] = { @@ -119,6 +128,14 @@ export class ThingSortingRule extends SortingRule { validate: strictArrayOf(isStringNonEmpty), }, }, + + // Expose only + + isThingSortingRule: [ + exposeConstant({ + value: input.value(true), + }), + ], }); static [Thing.yamlDocumentSpec] = Thing.extendDocumentSpec(SortingRule, { @@ -129,7 +146,7 @@ export class ThingSortingRule extends SortingRule { sort(sortable) { if (this.properties) { - for (const property of this.properties.slice().reverse()) { + for (const property of this.properties.toReversed()) { const get = thing => thing[property]; const lc = property.toLowerCase(); @@ -218,6 +235,14 @@ export class DocumentSortingRule extends ThingSortingRule { flags: {update: true, expose: true}, update: {validate: isStringNonEmpty}, }, + + // Expose only + + isDocumentSortingRule: [ + exposeConstant({ + value: input.value(true), + }), + ], }); static [Thing.yamlDocumentSpec] = Thing.extendDocumentSpec(ThingSortingRule, { @@ -261,10 +286,8 @@ export class DocumentSortingRule extends ThingSortingRule { } static async* applyAll(rules, {wikiData, dataPath, dry}) { - rules = - rules - .slice() - .sort((a, b) => a.filename.localeCompare(b.filename, 'en')); + rules = rules + .toSorted((a, b) => a.filename.localeCompare(b.filename, 'en')); for (const {chunk, filename} of chunkByProperties(rules, ['filename'])) { const initialLayout = getThingLayoutForFilename(filename, wikiData); diff --git a/src/data/things/static-page.js b/src/data/things/static-page.js index 52a09c31..28167df2 100644 --- a/src/data/things/static-page.js +++ b/src/data/things/static-page.js @@ -2,11 +2,13 @@ export const DATA_STATIC_PAGE_DIRECTORY = 'static-page'; import * as path from 'node:path'; +import {input} from '#composite'; import {traverse} from '#node-utils'; import {sortAlphabetically} from '#sort'; import Thing from '#thing'; import {isName} from '#validators'; +import {exposeConstant} from '#composite/control-flow'; import {contentString, directory, flag, name, simpleString} from '#composite/wiki-properties'; @@ -36,6 +38,14 @@ export class StaticPage extends Thing { content: contentString(), absoluteLinks: flag(), + + // Expose only + + isStaticPage: [ + exposeConstant({ + value: input.value(true), + }), + ], }); static [Thing.findSpecs] = { diff --git a/src/data/things/track.js b/src/data/things/track.js index 57aaa90d..64790a61 100644 --- a/src/data/things/track.js +++ b/src/data/things/track.js @@ -4,8 +4,16 @@ import CacheableObject from '#cacheable-object'; import {colors} from '#cli'; import {input} from '#composite'; import Thing from '#thing'; -import {isBoolean, isColor, isContributionList, isDate, isFileExtension} - from '#validators'; + +import { + isBoolean, + isColor, + isContentString, + isContributionList, + isDate, + isFileExtension, + validateReference, +} from '#validators'; import { parseAdditionalFiles, @@ -15,15 +23,17 @@ import { parseCommentary, parseContributors, parseCreditingSources, + parseReferencingSources, parseDate, parseDimensions, parseDuration, parseLyrics, } from '#yaml'; -import {withPropertyFromObject} from '#composite/data'; +import {withPropertyFromList, withPropertyFromObject} from '#composite/data'; import { + exitWithoutDependency, exposeConstant, exposeDependency, exposeDependencyOrContinue, @@ -38,8 +48,6 @@ import { } from '#composite/wiki-data'; import { - additionalFiles, - additionalNameList, commentatorArtists, constitutibleArtworkList, contentString, @@ -54,7 +62,6 @@ import { reverseReferenceList, simpleDate, simpleString, - singleReference, soupyFind, soupyReverse, thing, @@ -64,17 +71,18 @@ import { } from '#composite/wiki-properties'; import { + alwaysReferenceByDirectory, exitWithoutUniqueCoverArt, inheritContributionListFromMainRelease, inheritFromMainRelease, withAllReleases, - withAlwaysReferenceByDirectory, withContainingTrackSection, withCoverArtistContribs, withDate, withDirectorySuffix, withHasUniqueCoverArt, withMainRelease, + withMainReleaseTrack, withOtherReleases, withPropertyFromAlbum, withSuffixDirectoryFromAlbum, @@ -86,19 +94,27 @@ export class Track extends Thing { static [Thing.referenceType] = 'track'; static [Thing.getPropertyDescriptors] = ({ + AdditionalFile, + AdditionalName, Album, ArtTag, Artwork, CommentaryEntry, CreditingSourcesEntry, - Flash, LyricsEntry, - TrackSection, + ReferencingSourcesEntry, WikiInfo, }) => ({ - // Update & expose + // > Update & expose - Internal relationships + + album: thing({ + class: input.value(Album), + }), + + // > Update & expose - Identifying metadata name: name('Unnamed Track'), + nameText: contentString(), directory: [ withDirectorySuffix(), @@ -130,19 +146,120 @@ export class Track extends Thing { }) ], - album: thing({ - class: input.value(Album), - }), + alwaysReferenceByDirectory: alwaysReferenceByDirectory(), - additionalNames: additionalNameList(), + // Album or track. The exposed value is really just what's provided here, + // whether or not a matching track is found on a provided album, for + // example. When presenting or processing, read `mainReleaseTrack`. + mainRelease: [ + withMainRelease({ + from: input.updateValue({ + validate: + validateReference(['album', 'track']), + }), + }), + + exposeDependency({ + dependency: '#mainRelease', + }), + ], bandcampTrackIdentifier: simpleString(), bandcampArtworkIdentifier: simpleString(), - duration: duration(), - urls: urls(), + additionalNames: thingList({ + class: input.value(AdditionalName), + }), + dateFirstReleased: simpleDate(), + // > Update & expose - Credits and contributors + + artistText: [ + exposeUpdateValueOrContinue({ + validate: input.value(isContentString), + }), + + withPropertyFromAlbum({ + property: input.value('trackArtistText'), + }), + + exposeDependency({ + dependency: '#album.trackArtistText', + }), + ], + + artistContribs: [ + inheritContributionListFromMainRelease(), + + withDate(), + + withResolvedContribs({ + from: input.updateValue({validate: isContributionList}), + thingProperty: input.thisProperty(), + artistProperty: input.value('trackArtistContributions'), + date: '#date', + }).outputs({ + '#resolvedContribs': '#artistContribs', + }), + + exposeDependencyOrContinue({ + dependency: '#artistContribs', + mode: input.value('empty'), + }), + + withPropertyFromAlbum({ + property: input.value('trackArtistContribs'), + }), + + withRecontextualizedContributionList({ + list: '#album.trackArtistContribs', + artistProperty: input.value('trackArtistContributions'), + }), + + withRedatedContributionList({ + list: '#album.trackArtistContribs', + date: '#date', + }), + + exposeDependency({dependency: '#album.trackArtistContribs'}), + ], + + contributorContribs: [ + inheritContributionListFromMainRelease(), + + withDate(), + + contributionList({ + date: '#date', + artistProperty: input.value('trackContributorContributions'), + }), + ], + + // > Update & expose - General configuration + + countInArtistTotals: [ + exposeUpdateValueOrContinue({ + validate: input.value(isBoolean), + }), + + withContainingTrackSection(), + + withPropertyFromObject({ + object: '#trackSection', + property: input.value('countTracksInArtistTotals'), + }), + + exposeDependency({dependency: '#trackSection.countTracksInArtistTotals'}), + ], + + disableUniqueCoverArt: flag(), + disableDate: flag(), + + // > Update & expose - General metadata + + duration: duration(), + color: [ exposeUpdateValueOrContinue({ validate: input.value(isColor), @@ -164,37 +281,53 @@ export class Track extends Thing { exposeDependency({dependency: '#album.color'}), ], - alwaysReferenceByDirectory: [ - withAlwaysReferenceByDirectory(), - exposeDependency({dependency: '#alwaysReferenceByDirectory'}), - ], - - // Disables presenting the track as though it has its own unique artwork. - // This flag should only be used in select circumstances, i.e. to override - // an album's trackCoverArtists. This flag supercedes that property, as well - // as the track's own coverArtists. - disableUniqueCoverArt: flag(), + needsLyrics: [ + exposeUpdateValueOrContinue({ + mode: input.value('falsy'), + validate: input.value(isBoolean), + }), - // File extension for track's corresponding media file. This represents the - // track's unique cover artwork, if any, and does not inherit the extension - // of the album's main artwork. It does inherit trackCoverArtFileExtension, - // if present on the album. - coverArtFileExtension: [ - exitWithoutUniqueCoverArt(), + exitWithoutDependency({ + dependency: 'lyrics', + mode: input.value('empty'), + value: input.value(false), + }), - exposeUpdateValueOrContinue({ - validate: input.value(isFileExtension), + withPropertyFromList({ + list: 'lyrics', + property: input.value('helpNeeded'), }), - withPropertyFromAlbum({ - property: input.value('trackCoverArtFileExtension'), + { + dependencies: ['#lyrics.helpNeeded'], + compute: ({ + ['#lyrics.helpNeeded']: helpNeeded, + }) => + helpNeeded.includes(true) + }, + ], + + urls: urls(), + + // > Update & expose - Artworks + + trackArtworks: [ + exitWithoutUniqueCoverArt({ + value: input.value([]), }), - exposeDependencyOrContinue({dependency: '#album.trackCoverArtFileExtension'}), + constitutibleArtworkList.fromYAMLFieldSpec + .call(this, 'Track Artwork'), + ], - exposeConstant({ - value: input.value('jpg'), + coverArtistContribs: [ + withCoverArtistContribs({ + from: input.updateValue({ + validate: isContributionList, + }), }), + + exposeDependency({dependency: '#coverArtistContribs'}), ], coverArtDate: [ @@ -207,103 +340,68 @@ export class Track extends Thing { exposeDependency({dependency: '#trackArtDate'}), ], - coverArtDimensions: [ + coverArtFileExtension: [ exitWithoutUniqueCoverArt(), - exposeUpdateValueOrContinue(), + exposeUpdateValueOrContinue({ + validate: input.value(isFileExtension), + }), withPropertyFromAlbum({ - property: input.value('trackDimensions'), + property: input.value('trackCoverArtFileExtension'), }), - exposeDependencyOrContinue({dependency: '#album.trackDimensions'}), - - dimensions(), - ], - - commentary: thingList({ - class: input.value(CommentaryEntry), - }), - - creditSources: thingList({ - class: input.value(CreditingSourcesEntry), - }), - - lyrics: [ - // TODO: Inherited lyrics are literally the same objects, so of course - // their .thing properties aren't going to point back to this one, and - // certainly couldn't be recontextualized... - inheritFromMainRelease(), + exposeDependencyOrContinue({dependency: '#album.trackCoverArtFileExtension'}), - thingList({ - class: input.value(LyricsEntry), + exposeConstant({ + value: input.value('jpg'), }), ], - additionalFiles: additionalFiles(), - sheetMusicFiles: additionalFiles(), - midiProjectFiles: additionalFiles(), - - mainReleaseTrack: singleReference({ - class: input.value(Track), - find: soupyFind.input('track'), - }), - - artistContribs: [ - inheritContributionListFromMainRelease(), + coverArtDimensions: [ + exitWithoutUniqueCoverArt(), - withDate(), + exposeUpdateValueOrContinue(), - withResolvedContribs({ - from: input.updateValue({validate: isContributionList}), - thingProperty: input.thisProperty(), - artistProperty: input.value('trackArtistContributions'), - date: '#date', - }).outputs({ - '#resolvedContribs': '#artistContribs', + withPropertyFromAlbum({ + property: input.value('trackDimensions'), }), - exposeDependencyOrContinue({ - dependency: '#artistContribs', - mode: input.value('empty'), - }), + exposeDependencyOrContinue({dependency: '#album.trackDimensions'}), - withPropertyFromAlbum({ - property: input.value('artistContribs'), + dimensions(), + ], + + artTags: [ + exitWithoutUniqueCoverArt({ + value: input.value([]), }), - withRecontextualizedContributionList({ - list: '#album.artistContribs', - artistProperty: input.value('trackArtistContributions'), + referenceList({ + class: input.value(ArtTag), + find: soupyFind.input('artTag'), }), + ], - withRedatedContributionList({ - list: '#album.artistContribs', - date: '#date', + referencedArtworks: [ + exitWithoutUniqueCoverArt({ + value: input.value([]), }), - exposeDependency({dependency: '#album.artistContribs'}), + referencedArtworkList(), ], - contributorContribs: [ - inheritContributionListFromMainRelease(), - - withDate(), + // > Update & expose - Referenced tracks - contributionList({ - date: '#date', - artistProperty: input.value('trackContributorContributions'), + previousProductionTracks: [ + inheritFromMainRelease({ + notFoundValue: input.value([]), }), - ], - coverArtistContribs: [ - withCoverArtistContribs({ - from: input.updateValue({ - validate: isContributionList, - }), + referenceList({ + class: input.value(Track), + find: soupyFind.input('trackMainReleasesOnly'), }), - - exposeDependency({dependency: '#coverArtistContribs'}), ], referencedTracks: [ @@ -313,7 +411,7 @@ export class Track extends Thing { referenceList({ class: input.value(Track), - find: soupyFind.input('track'), + find: soupyFind.input('trackMainReleasesOnly'), }), ], @@ -324,39 +422,50 @@ export class Track extends Thing { referenceList({ class: input.value(Track), - find: soupyFind.input('track'), + find: soupyFind.input('trackMainReleasesOnly'), }), ], - trackArtworks: [ - exitWithoutUniqueCoverArt({ - value: input.value([]), - }), + // > Update & expose - Additional files - constitutibleArtworkList.fromYAMLFieldSpec - .call(this, 'Track Artwork'), - ], + additionalFiles: thingList({ + class: input.value(AdditionalFile), + }), - artTags: [ - exitWithoutUniqueCoverArt({ - value: input.value([]), - }), + sheetMusicFiles: thingList({ + class: input.value(AdditionalFile), + }), - referenceList({ - class: input.value(ArtTag), - find: soupyFind.input('artTag'), + midiProjectFiles: thingList({ + class: input.value(AdditionalFile), + }), + + // > Update & expose - Content entries + + lyrics: [ + // TODO: Inherited lyrics are literally the same objects, so of course + // their .thing properties aren't going to point back to this one, and + // certainly couldn't be recontextualized... + inheritFromMainRelease(), + + thingList({ + class: input.value(LyricsEntry), }), ], - referencedArtworks: [ - exitWithoutUniqueCoverArt({ - value: input.value([]), - }), + commentary: thingList({ + class: input.value(CommentaryEntry), + }), - referencedArtworkList(), - ], + creditingSources: thingList({ + class: input.value(CreditingSourcesEntry), + }), - // Update only + referencingSources: thingList({ + class: input.value(ReferencingSourcesEntry), + }), + + // > Update only find: soupyFind(), reverse: soupyReverse(), @@ -366,17 +475,18 @@ export class Track extends Thing { class: input.value(Artwork), }), - // used for withAlwaysReferenceByDirectory (for some reason) - trackData: wikiData({ - class: input.value(Track), - }), - // used for withMatchingContributionPresets (indirectly by Contribution) wikiInfo: thing({ class: input.value(WikiInfo), }), - // Expose only + // > Expose only + + isTrack: [ + exposeConstant({ + value: input.value(true), + }), + ], commentatorArtists: commentatorArtists(), @@ -396,19 +506,27 @@ export class Track extends Thing { ], isMainRelease: [ - withMainRelease(), + withMainReleaseTrack(), exposeWhetherDependencyAvailable({ - dependency: '#mainRelease', + dependency: '#mainReleaseTrack', negate: input.value(true), }), ], isSecondaryRelease: [ - withMainRelease(), + withMainReleaseTrack(), exposeWhetherDependencyAvailable({ - dependency: '#mainRelease', + dependency: '#mainReleaseTrack', + }), + ], + + mainReleaseTrack: [ + withMainReleaseTrack(), + + exposeDependency({ + dependency: '#mainReleaseTrack', }), ], @@ -428,6 +546,38 @@ export class Track extends Thing { exposeDependency({dependency: '#otherReleases'}), ], + commentaryFromMainRelease: [ + withMainReleaseTrack(), + + exitWithoutDependency({ + dependency: '#mainReleaseTrack', + value: input.value([]), + }), + + withPropertyFromObject({ + object: '#mainReleaseTrack', + property: input.value('commentary'), + }), + + exposeDependency({ + dependency: '#mainReleaseTrack.commentary', + }), + ], + + groups: [ + withPropertyFromAlbum({ + property: input.value('groups'), + }), + + exposeDependency({ + dependency: '#album.groups', + }), + ], + + followingProductionTracks: reverseReferenceList({ + reverse: soupyReverse.input('tracksWhichAreFollowingProductionsOf'), + }), + referencedByTracks: reverseReferenceList({ reverse: soupyReverse.input('tracksWhichReference'), }), @@ -443,14 +593,14 @@ export class Track extends Thing { static [Thing.yamlDocumentSpec] = { fields: { + // Identifying metadata + 'Track': {property: 'name'}, + 'Track Text': {property: 'nameText'}, 'Directory': {property: 'directory'}, 'Suffix Directory': {property: 'suffixDirectoryFromAlbum'}, - - 'Additional Names': { - property: 'additionalNames', - transform: parseAdditionalNames, - }, + 'Always Reference By Directory': {property: 'alwaysReferenceByDirectory'}, + 'Main Release': {property: 'mainRelease'}, 'Bandcamp Track ID': { property: 'bandcampTrackIdentifier', @@ -462,31 +612,36 @@ export class Track extends Thing { transform: String, }, - 'Duration': { - property: 'duration', - transform: parseDuration, + 'Additional Names': { + property: 'additionalNames', + transform: parseAdditionalNames, }, - 'Color': {property: 'color'}, - 'URLs': {property: 'urls'}, - 'Date First Released': { property: 'dateFirstReleased', transform: parseDate, }, - 'Cover Art Date': { - property: 'coverArtDate', - transform: parseDate, + // Credits and contributors + + 'Artist Text': { + property: 'artistText', }, - 'Cover Art File Extension': {property: 'coverArtFileExtension'}, + 'Artists': { + property: 'artistContribs', + transform: parseContributors, + }, - 'Cover Art Dimensions': { - property: 'coverArtDimensions', - transform: parseDimensions, + 'Contributors': { + property: 'contributorContribs', + transform: parseContributors, }, + // General configuration + + 'Count In Artist Totals': {property: 'countInArtistTotals'}, + 'Has Cover Art': { property: 'disableUniqueCoverArt', transform: value => @@ -495,23 +650,78 @@ export class Track extends Thing { : value), }, - 'Always Reference By Directory': {property: 'alwaysReferenceByDirectory'}, + 'Has Date': { + property: 'disableDate', + transform: value => + (typeof value === 'boolean' + ? !value + : value), + }, - 'Lyrics': { - property: 'lyrics', - transform: parseLyrics, + // General metadata + + 'Duration': { + property: 'duration', + transform: parseDuration, }, - 'Commentary': { - property: 'commentary', - transform: parseCommentary, + 'Color': {property: 'color'}, + + 'Needs Lyrics': { + property: 'needsLyrics', }, - 'Credit Sources': { - property: 'creditSources', - transform: parseCreditingSources, + 'URLs': {property: 'urls'}, + + // Artworks + + 'Track Artwork': { + property: 'trackArtworks', + transform: + parseArtwork({ + thingProperty: 'trackArtworks', + dimensionsFromThingProperty: 'coverArtDimensions', + fileExtensionFromThingProperty: 'coverArtFileExtension', + dateFromThingProperty: 'coverArtDate', + artTagsFromThingProperty: 'artTags', + referencedArtworksFromThingProperty: 'referencedArtworks', + artistContribsFromThingProperty: 'coverArtistContribs', + artistContribsArtistProperty: 'trackCoverArtistContributions', + }), + }, + + 'Cover Artists': { + property: 'coverArtistContribs', + transform: parseContributors, }, + 'Cover Art Date': { + property: 'coverArtDate', + transform: parseDate, + }, + + 'Cover Art File Extension': {property: 'coverArtFileExtension'}, + + 'Cover Art Dimensions': { + property: 'coverArtDimensions', + transform: parseDimensions, + }, + + 'Art Tags': {property: 'artTags'}, + + 'Referenced Artworks': { + property: 'referencedArtworks', + transform: parseAnnotatedReferences, + }, + + // Referenced tracks + + 'Previous Productions': {property: 'previousProductionTracks'}, + 'Referenced Tracks': {property: 'referencedTracks'}, + 'Sampled Tracks': {property: 'sampledTracks'}, + + // Additional files + 'Additional Files': { property: 'additionalFiles', transform: parseAdditionalFiles, @@ -527,54 +737,41 @@ export class Track extends Thing { transform: parseAdditionalFiles, }, - 'Main Release': {property: 'mainReleaseTrack'}, - 'Referenced Tracks': {property: 'referencedTracks'}, - 'Sampled Tracks': {property: 'sampledTracks'}, - - 'Referenced Artworks': { - property: 'referencedArtworks', - transform: parseAnnotatedReferences, - }, - - 'Franchises': {ignore: true}, - 'Inherit Franchises': {ignore: true}, + // Content entries - 'Artists': { - property: 'artistContribs', - transform: parseContributors, + 'Lyrics': { + property: 'lyrics', + transform: parseLyrics, }, - 'Contributors': { - property: 'contributorContribs', - transform: parseContributors, + 'Commentary': { + property: 'commentary', + transform: parseCommentary, }, - 'Cover Artists': { - property: 'coverArtistContribs', - transform: parseContributors, + 'Crediting Sources': { + property: 'creditingSources', + transform: parseCreditingSources, }, - 'Track Artwork': { - property: 'trackArtworks', - transform: - parseArtwork({ - thingProperty: 'trackArtworks', - dimensionsFromThingProperty: 'coverArtDimensions', - fileExtensionFromThingProperty: 'coverArtFileExtension', - dateFromThingProperty: 'coverArtDate', - artTagsFromThingProperty: 'artTags', - referencedArtworksFromThingProperty: 'referencedArtworks', - artistContribsFromThingProperty: 'coverArtistContribs', - artistContribsArtistProperty: 'trackCoverArtistContributions', - }), + 'Referencing Sources': { + property: 'referencingSources', + transform: parseReferencingSources, }, - 'Art Tags': {property: 'artTags'}, + // Shenanigans + 'Franchises': {ignore: true}, + 'Inherit Franchises': {ignore: true}, 'Review Points': {ignore: true}, }, invalidFieldCombinations: [ + {message: `Secondary releases never count in artist totals`, fields: [ + 'Main Release', + 'Count In Artist Totals', + ]}, + {message: `Secondary releases inherit references from the main one`, fields: [ 'Main Release', 'Referenced Tracks', @@ -631,7 +828,7 @@ export class Track extends Thing { bindTo: 'trackData', include: track => - !CacheableObject.getUpdateValue(track, 'mainReleaseTrack'), + !CacheableObject.getUpdateValue(track, 'mainRelease'), // It's still necessary to check alwaysReferenceByDirectory here, since // it may be set manually (with `Always Reference By Directory: true`), @@ -731,11 +928,28 @@ export class Track extends Thing { referencing: track => track.isSecondaryRelease ? [track] : [], referenced: track => [track.mainReleaseTrack], }, + + tracksWhichAreFollowingProductionsOf: { + bindTo: 'trackData', + + referencing: track => track, + referenced: track => track.previousProductionTracks, + }, }; // Track YAML loading is handled in album.js. static [Thing.getYamlLoadingSpec] = null; + getOwnAdditionalFilePath(_file, filename) { + if (!this.album) return null; + + return [ + 'media.albumAdditionalFile', + this.album.directory, + filename, + ]; + } + getOwnArtworkPath(artwork) { if (!this.album) return null; @@ -751,12 +965,36 @@ export class Track extends Thing { ]; } + countOwnContributionInContributionTotals(_contrib) { + if (!this.countInArtistTotals) { + return false; + } + + if (this.isSecondaryRelease) { + return false; + } + + return true; + } + + countOwnContributionInDurationTotals(_contrib) { + if (!this.countInArtistTotals) { + return false; + } + + if (this.isSecondaryRelease) { + return false; + } + + return true; + } + [inspect.custom](depth) { const parts = []; parts.push(Thing.prototype[inspect.custom].apply(this)); - if (CacheableObject.getUpdateValue(this, 'mainReleaseTrack')) { + if (CacheableObject.getUpdateValue(this, 'mainRelease')) { parts.unshift(`${colors.yellow('[secrelease]')} `); } diff --git a/src/data/things/wiki-info.js b/src/data/things/wiki-info.js index 590598be..7fb6a350 100644 --- a/src/data/things/wiki-info.js +++ b/src/data/things/wiki-info.js @@ -2,7 +2,7 @@ export const WIKI_INFO_FILE = 'wiki-info.yaml'; import {input} from '#composite'; import Thing from '#thing'; -import {parseContributionPresets} from '#yaml'; +import {parseContributionPresets, parseWallpaperParts} from '#yaml'; import { isBoolean, @@ -10,12 +10,21 @@ import { isContributionPresetList, isLanguageCode, isName, - isURL, } from '#validators'; -import {exitWithoutDependency} from '#composite/control-flow'; -import {contentString, flag, name, referenceList, soupyFind} - from '#composite/wiki-properties'; +import {exitWithoutDependency, exposeConstant} from '#composite/control-flow'; + +import { + canonicalBase, + contentString, + fileExtension, + flag, + name, + referenceList, + simpleString, + soupyFind, + wallpaperParts, +} from '#composite/wiki-properties'; export class WikiInfo extends Thing { static [Thing.friendlyName] = `Wiki Info`; @@ -55,18 +64,12 @@ export class WikiInfo extends Thing { update: {validate: isLanguageCode}, }, - canonicalBase: { - flags: {update: true, expose: true}, - update: {validate: isURL}, - expose: { - transform: (value) => - (value === null - ? null - : value.endsWith('/') - ? value - : value + '/'), - }, - }, + canonicalBase: canonicalBase(), + canonicalMediaBase: canonicalBase(), + + wikiWallpaperFileExtension: fileExtension('jpg'), + wikiWallpaperStyle: simpleString(), + wikiWallpaperParts: wallpaperParts(), divideTrackListsByGroups: referenceList({ class: input.value(Group), @@ -106,24 +109,49 @@ export class WikiInfo extends Thing { default: false, }, }, + + // Expose only + + isWikiInfo: [ + exposeConstant({ + value: input.value(true), + }), + ], }); static [Thing.yamlDocumentSpec] = { fields: { 'Name': {property: 'name'}, 'Short Name': {property: 'nameShort'}, + 'Color': {property: 'color'}, + 'Description': {property: 'description'}, + 'Footer Content': {property: 'footerContent'}, + 'Default Language': {property: 'defaultLanguage'}, + 'Canonical Base': {property: 'canonicalBase'}, - 'Divide Track Lists By Groups': {property: 'divideTrackListsByGroups'}, + 'Canonical Media Base': {property: 'canonicalMediaBase'}, + + 'Wiki Wallpaper File Extension': {property: 'wikiWallpaperFileExtension'}, + + 'Wiki Wallpaper Style': {property: 'wikiWallpaperStyle'}, + + 'Wiki Wallpaper Parts': { + property: 'wikiWallpaperParts', + transform: parseWallpaperParts, + }, + 'Enable Flashes & Games': {property: 'enableFlashesAndGames'}, 'Enable Listings': {property: 'enableListings'}, 'Enable News': {property: 'enableNews'}, 'Enable Art Tag UI': {property: 'enableArtTagUI'}, 'Enable Group UI': {property: 'enableGroupUI'}, + 'Divide Track Lists By Groups': {property: 'divideTrackListsByGroups'}, + 'Contribution Presets': { property: 'contributionPresets', transform: parseContributionPresets, diff --git a/src/data/yaml.js b/src/data/yaml.js index 036fe8a7..46cb4eda 100644 --- a/src/data/yaml.js +++ b/src/data/yaml.js @@ -8,6 +8,7 @@ import {inspect as nodeInspect} from 'node:util'; import yaml from 'js-yaml'; import {colors, ENABLE_COLOR, logInfo, logWarn} from '#cli'; +import {parseContentNodes, splitContentNodesAround} from '#replacer'; import {sortByName} from '#sort'; import Thing from '#thing'; import thingConstructors from '#things'; @@ -86,7 +87,7 @@ function makeProcessDocument(thingConstructor, { // ] // // ...means A can't coexist with B or C, B can't coexist with A or C, and - // C can't coexist iwth A, B, or D - but it's okay for D to coexist with + // C can't coexist with A, B, or D - but it's okay for D to coexist with // A or B. // invalidFieldCombinations = [], @@ -181,9 +182,22 @@ function makeProcessDocument(thingConstructor, { const fieldCombinationErrors = []; - for (const {message, fields} of invalidFieldCombinations) { + for (const {message, fields: fieldsSpec} of invalidFieldCombinations) { const fieldsPresent = - presentFields.filter(field => fields.includes(field)); + fieldsSpec.flatMap(fieldSpec => { + if (Array.isArray(fieldSpec)) { + const [field, match] = fieldSpec; + if (!presentFields.includes(field)) return []; + if (typeof match === 'function') { + return match(document[field]) ? [field] : []; + } else { + return document[field] === match ? [field] : []; + } + } + + const field = fieldSpec; + return presentFields.includes(field) ? [field] : []; + }); if (fieldsPresent.length >= 2) { const filteredDocument = @@ -193,7 +207,10 @@ function makeProcessDocument(thingConstructor, { {preserveOriginalOrder: true}); fieldCombinationErrors.push( - new FieldCombinationError(filteredDocument, message)); + new FieldCombinationError( + filteredDocument, + fieldsSpec, + message)); for (const field of Object.keys(filteredDocument)) { skippedFields.add(field); @@ -249,7 +266,9 @@ function makeProcessDocument(thingConstructor, { // This variable would like to certify itself as "not into capitalism". let propertyValue = - (fieldSpecs[field].transform + (documentValue === null + ? null + : fieldSpecs[field].transform ? fieldSpecs[field].transform(documentValue, transformUtilities) : documentValue); @@ -415,19 +434,36 @@ export class FieldCombinationAggregateError extends AggregateError { } export class FieldCombinationError extends Error { - constructor(fields, message) { - const fieldNames = Object.keys(fields); + constructor(filteredDocument, fieldsSpec, message) { + const fieldNames = Object.keys(filteredDocument); const fieldNamesText = fieldNames - .map(field => colors.red(field)) + .map(field => { + if (fieldsSpec.includes(field)) { + return colors.red(field); + } + + const match = + fieldsSpec + .find(fieldSpec => + Array.isArray(fieldSpec) && + fieldSpec[0] === field) + .at(1); + + if (typeof match === 'function') { + return colors.red(`${field}: ${filteredDocument[field]}`); + } else { + return colors.red(`${field}: ${match}`); + } + }) .join(', '); const mainMessage = `Don't combine ${fieldNamesText}`; const causeMessage = (typeof message === 'function' - ? message(fields) + ? message(filteredDocument) : typeof message === 'string' ? message : null); @@ -439,7 +475,7 @@ export class FieldCombinationError extends Error { : null), }); - this.fields = fields; + this.fields = fieldNames; } } @@ -610,49 +646,39 @@ export function parseContributors(entries) { }); } -export function parseAdditionalFiles(entries) { +export function parseAdditionalFiles(entries, {subdoc, AdditionalFile}) { return parseArrayEntries(entries, item => { if (typeof item !== 'object') return item; - return { - title: item['Title'], - description: item['Description'] ?? null, - files: item['Files'], - }; + return subdoc(AdditionalFile, item, {bindInto: 'thing'}); }); } -export function parseAdditionalNames(entries) { +export function parseAdditionalNames(entries, {subdoc, AdditionalName}) { return parseArrayEntries(entries, item => { - if (typeof item === 'object' && typeof item['Name'] === 'string') - return { - name: item['Name'], - annotation: item['Annotation'] ?? null, - }; + if (typeof item === 'object') { + return subdoc(AdditionalName, item, {bindInto: 'thing'}); + } if (typeof item !== 'string') return item; const match = item.match(extractAccentRegex); if (!match) return item; - return { - name: match.groups.main, - annotation: match.groups.accent ?? null, + const document = { + ['Name']: match.groups.main, + ['Annotation']: match.groups.accent ?? null, }; + + return subdoc(AdditionalName, document, {bindInto: 'thing'}); }); } -export function parseSerieses(entries) { +export function parseSerieses(entries, {subdoc, Series}) { return parseArrayEntries(entries, item => { if (typeof item !== 'object') return item; - return { - name: item['Name'], - description: item['Description'] ?? null, - albums: item['Albums'] ?? null, - - showAlbumArtists: item['Show Album Artists'] ?? null, - }; + return subdoc(Series, item, {bindInto: 'group'}); }); } @@ -827,37 +853,82 @@ export function parseArtwork({ return transform; } -export function parseContentEntries(thingClass, sourceText, {subdoc}) { - const map = matchEntry => ({ - 'Artists': - matchEntry.artistReferences - .split(',') - .map(ref => ref.trim()), +export function parseContentEntriesFromSourceText(thingClass, sourceText, {subdoc}) { + function map(matchEntry) { + let artistText = null, artistReferences = null; - 'Artist Text': - matchEntry.artistDisplayText, + const artistTextNodes = + Array.from( + splitContentNodesAround( + parseContentNodes(matchEntry.artistText), + /\|/g)); - 'Annotation': - matchEntry.annotation, + const separatorIndices = + artistTextNodes + .filter(node => node.type === 'separator') + .map(node => artistTextNodes.indexOf(node)); - 'Date': - matchEntry.date, + if (empty(separatorIndices)) { + if (artistTextNodes.length === 1 && artistTextNodes[0].type === 'text') { + artistReferences = matchEntry.artistText; + } else { + artistText = matchEntry.artistText; + } + } else { + const firstSeparatorIndex = + separatorIndices.at(0); + + const secondSeparatorIndex = + separatorIndices.at(1) ?? + artistTextNodes.length; + + artistReferences = + matchEntry.artistText.slice( + artistTextNodes.at(0).i, + artistTextNodes.at(firstSeparatorIndex - 1).iEnd); + + artistText = + matchEntry.artistText.slice( + artistTextNodes.at(firstSeparatorIndex).iEnd, + artistTextNodes.at(secondSeparatorIndex - 1).iEnd); + } - 'Second Date': - matchEntry.secondDate, + if (artistReferences) { + artistReferences = + artistReferences + .split(',') + .map(ref => ref.trim()); + } - 'Date Kind': - matchEntry.dateKind, + return { + 'Artists': + artistReferences, - 'Access Date': - matchEntry.accessDate, + 'Artist Text': + artistText, - 'Access Kind': - matchEntry.accessKind, + 'Annotation': + matchEntry.annotation, - 'Body': - matchEntry.body, - }); + 'Date': + matchEntry.date, + + 'Second Date': + matchEntry.secondDate, + + 'Date Kind': + matchEntry.dateKind, + + 'Access Date': + matchEntry.accessDate, + + 'Access Kind': + matchEntry.accessKind, + + 'Body': + matchEntry.body, + }; + } const documents = matchContentEntries(sourceText) @@ -876,22 +947,39 @@ export function parseContentEntries(thingClass, sourceText, {subdoc}) { return subdocs; } -export function parseCommentary(sourceText, {subdoc, CommentaryEntry}) { - return parseContentEntries(CommentaryEntry, sourceText, {subdoc}); +export function parseContentEntries(thingClass, value, {subdoc}) { + if (typeof value === 'string') { + return parseContentEntriesFromSourceText(thingClass, value, {subdoc}); + } else if (Array.isArray(value)) { + return value.map(doc => subdoc(thingClass, doc, {bindInto: 'thing'})); + } else { + return value; + } } -export function parseCreditingSources(sourceText, {subdoc, CreditingSourcesEntry}) { - return parseContentEntries(CreditingSourcesEntry, sourceText, {subdoc}); +export function parseCommentary(value, {subdoc, CommentaryEntry}) { + return parseContentEntries(CommentaryEntry, value, {subdoc}); } -export function parseLyrics(sourceText, {subdoc, LyricsEntry}) { - if (!multipleLyricsDetectionRegex.test(sourceText)) { - const document = {'Body': sourceText}; +export function parseCreditingSources(value, {subdoc, CreditingSourcesEntry}) { + return parseContentEntries(CreditingSourcesEntry, value, {subdoc}); +} + +export function parseReferencingSources(value, {subdoc, ReferencingSourcesEntry}) { + return parseContentEntries(ReferencingSourcesEntry, value, {subdoc}); +} + +export function parseLyrics(value, {subdoc, LyricsEntry}) { + if ( + typeof value === 'string' && + !multipleLyricsDetectionRegex.test(value) + ) { + const document = {'Body': value}; return [subdoc(LyricsEntry, document, {bindInto: 'thing'})]; } - return parseContentEntries(LyricsEntry, sourceText, {subdoc}); + return parseContentEntries(LyricsEntry, value, {subdoc}); } // documentModes: Symbols indicating sets of behavior for loading and processing @@ -923,6 +1011,12 @@ export const documentModes = { // array of processed documents (wiki objects). allInOne: Symbol('Document mode: allInOne'), + // allTogether: One or more documens, spread across any number of files. + // Expects files array (or function) and processDocument function. + // Calls save with an array of processed documents (wiki objects) - this is + // a flat array, *not* an array of the documents processed from *each* file. + allTogether: Symbol('Document mode: allTogether'), + // oneDocumentTotal: Just a single document, represented in one file. // Expects file string (or function) and processDocument function. Calls // save with the single processed wiki document (data object). @@ -969,7 +1063,7 @@ export const documentModes = { export function getAllDataSteps() { try { thingConstructors; - } catch (error) { + } catch { throw new Error(`Thing constructors aren't ready yet, can't get all data steps`); } @@ -1033,6 +1127,7 @@ export async function getFilesFromDataStep(dataStep, {dataPath}) { } } + case documentModes.allTogether: case documentModes.headerAndEntries: case documentModes.onePerFile: { if (!dataStep.files) { @@ -1188,7 +1283,8 @@ export function processThingsFromDataStep(documents, dataStep) { const {documentMode} = dataStep; switch (documentMode) { - case documentModes.allInOne: { + case documentModes.allInOne: + case documentModes.allTogether: { const result = []; const aggregate = openAggregate({message: `Errors processing documents`}); @@ -1463,6 +1559,10 @@ export function saveThingsFromDataStep(thingLists, dataStep) { return dataStep.save(thing); } + case documentModes.allTogether: { + return dataStep.save(thingLists.flat()); + } + case documentModes.headerAndEntries: case documentModes.onePerFile: { return dataStep.save(thingLists); @@ -1589,9 +1689,12 @@ export function linkWikiDataArrays(wikiData, {bindFind, bindReverse}) { ['lyricsData', [/* find */]], + ['referencingSourceData', [/* find */]], + + ['seriesData', [/* find */]], + ['trackData', [ 'artworkData', - 'trackData', 'wikiInfo', ]], diff --git a/src/external-links.js b/src/external-links.js index 1055a391..06570067 100644 --- a/src/external-links.js +++ b/src/external-links.js @@ -4,11 +4,9 @@ import { anyOf, is, isBoolean, - isObject, isStringNonEmpty, looseArrayOf, optional, - validateAllPropertyValues, validateArrayItems, validateInstanceOf, validateProperties, @@ -32,6 +30,9 @@ export const externalLinkContexts = [ 'generic', 'group', 'track', + + 'composerRelease', + 'officialRelease', ]; export const isExternalLinkContext = @@ -257,6 +258,30 @@ export const externalLinkSpec = [ }, { + match: { + domain: '.bandcamp.com', + context: 'composerRelease', + }, + + platform: 'bandcamp.composerRelease', + handle: {domain: /^[^.]+/}, + + icon: 'bandcamp', + }, + + { + match: { + domain: '.bandcamp.com', + context: 'officialRelease', + }, + + platform: 'bandcamp.officialRelease', + handle: {domain: /^[^.]+/}, + + icon: 'bandcamp', + }, + + { match: {domain: '.bandcamp.com'}, platform: 'bandcamp', @@ -557,6 +582,17 @@ export const externalLinkSpec = [ }, { + match: {domains: ['reddit.com', 'old.reddit.com']}, + platform: 'reddit', + icon: 'globe', + + detail: { + substring: 'subreddit', + subreddit: {pathname: /^r\/[^/]+(?=\/)?/}, + }, + }, + + { match: {domain: 'soundcloud.com'}, platform: 'soundcloud', diff --git a/src/find-reverse.js b/src/find-reverse.js index 6a67ac0f..c99a4a71 100644 --- a/src/find-reverse.js +++ b/src/find-reverse.js @@ -11,7 +11,7 @@ export function getAllSpecs({ }) { try { thingConstructors; - } catch (error) { + } catch { throw new Error(`Thing constructors aren't ready yet, can't get all ${word} specs`); } @@ -52,7 +52,7 @@ export function findSpec(key, { try { thingConstructors; - } catch (error) { + } catch { throw new Error(`Thing constructors aren't ready yet, can't check if "${word}.${key}" available`); } diff --git a/src/find.js b/src/find.js index e7f5cda1..7b605e97 100644 --- a/src/find.js +++ b/src/find.js @@ -4,6 +4,7 @@ import {colors, logWarn} from '#cli'; import {compareObjects, stitchArrays, typeAppearance} from '#sugar'; import thingConstructors from '#things'; import {isFunction, validateArrayItems} from '#validators'; +import {getCaseSensitiveKebabCase} from '#wiki-data'; import * as fr from './find-reverse.js'; @@ -30,7 +31,34 @@ function warnOrThrow(mode, message) { export const keyRefRegex = new RegExp(String.raw`^(?:(?<key>[a-z-]*):(?=\S))?(?<ref>.*)$`); -export function processAvailableMatchesByName(data, { +function getFuzzHash(fuzz = {}) { + if (!fuzz) { + return 0; + } + + return ( + fuzz.capitalization << 0 + + fuzz.kebab << 1 + ); +} + +export function fuzzName(name, fuzz = {}) { + if (!fuzz) { + return name; + } + + if (fuzz.capitalization) { + name = name.toLowerCase(); + } + + if (fuzz.kebab) { + name = getCaseSensitiveKebabCase(name); + } + + return name; +} + +export function processAvailableMatchesByName(data, fuzz, { include = _thing => true, getMatchableNames = thing => @@ -50,17 +78,20 @@ export function processAvailableMatchesByName(data, { continue; } - const normalizedName = name.toLowerCase(); + const normalizedName = fuzzName(name, fuzz); if (normalizedName in results) { if (normalizedName in multipleNameMatches) { multipleNameMatches[normalizedName].push(thing); } else { - multipleNameMatches[normalizedName] = [results[normalizedName], thing]; + multipleNameMatches[normalizedName] = [ + results[normalizedName].thing, + thing, + ]; results[normalizedName] = null; } } else { - results[normalizedName] = thing; + results[normalizedName] = {thing, name}; } } } @@ -87,16 +118,16 @@ export function processAvailableMatchesByDirectory(data, { continue; } - results[directory] = thing; + results[directory] = {thing, directory}; } } return {results}; } -export function processAllAvailableMatches(data, spec) { +export function processAllAvailableMatches(data, fuzz, spec) { const {results: byName, multipleNameMatches} = - processAvailableMatchesByName(data, spec); + processAvailableMatchesByName(data, fuzz, spec); const {results: byDirectory} = processAvailableMatchesByDirectory(data, spec); @@ -109,21 +140,69 @@ function oopsMultipleNameMatches(mode, { normalizedName, multipleNameMatches, }) { + try { + return warnOrThrow(mode, + `Multiple matches for reference "${name}". Please resolve:\n` + + multipleNameMatches[normalizedName] + .map(match => `- ${inspect(match)}\n`) + .join('') + + `Returning null for this reference.`); + } catch (caughtError) { + throw Object.assign(caughtError, { + [Symbol.for('hsmusic.find.multipleNameMatches')]: + multipleNameMatches[normalizedName], + }); + } +} + +function oopsNameCapitalizationMismatch(mode, { + matchingName, + matchedName, +}) { + if (matchingName.length === matchedName.length) { + let a = '', b = ''; + for (let i = 0; i < matchingName.length; i++) { + if ( + matchingName[i] === matchedName[i] || + matchingName[i].toLowerCase() !== matchingName[i].toLowerCase() + ) { + a += matchingName[i]; + b += matchedName[i]; + } else { + a += colors.bright(colors.red(matchingName[i])); + b += colors.bright(colors.green(matchedName[i])); + } + } + + matchingName = a; + matchedName = b; + } + return warnOrThrow(mode, - `Multiple matches for reference "${name}". Please resolve:\n` + - multipleNameMatches[normalizedName] - .map(match => `- ${inspect(match)}\n`) - .join('') + + `Provided capitalization differs from the matched name. Please resolve:\n` + + `- provided: ${matchingName}\n` + + `- should be: ${matchedName}\n` + `Returning null for this reference.`); } -export function prepareMatchByName(mode, {byName, multipleNameMatches}) { +export function prepareMatchByName(mode, fuzz, {byName, multipleNameMatches}) { return (name) => { - const normalizedName = name.toLowerCase(); + const normalizedName = fuzzName(name, fuzz); const match = byName[normalizedName]; if (match) { - return match; + if ( + !fuzz?.capitalization && + name !== match.name && + name.toLowerCase === match.name.toLowerCase() + ) { + return oopsNameCapitalizationMismatch(mode, { + matchingName: name, + matchedName: match.name, + }); + } else { + return match.thing; + } } else if (multipleNameMatches[normalizedName]) { return oopsMultipleNameMatches(mode, { name, @@ -154,7 +233,13 @@ export function prepareMatchByDirectory(mode, {referenceTypes, byDirectory}) { }); } - return byDirectory[directory]; + const match = byDirectory[directory]; + + if (match) { + return match.thing; + } else { + return null; + } }; } @@ -196,11 +281,18 @@ function findHelper({ // hasn't changed! const cache = new WeakMap(); - // The mode argument here may be 'warn', 'error', or 'quiet'. 'error' throws - // errors for null matches (with details about the error), while 'warn' and - // 'quiet' both return null, with 'warn' logging details directly to the - // console. - return (fullRef, data, {mode = 'warn'} = {}) => { + return (fullRef, data, { + // The mode argument here may be 'warn', 'error', or 'quiet'. 'error' throws + // errors for null matches (with details about the error), while 'warn' and + // 'quiet' both return null, with 'warn' logging details directly to the + // console. + mode = 'warn', + + fuzz = { + capitalization: false, + kebab: false, + }, + } = {}) => { if (!fullRef) return null; if (typeof fullRef !== 'string') { @@ -211,19 +303,23 @@ function findHelper({ throw new TypeError(`Expected data to be present`); } - let subcache = cache.get(data); - if (!subcache) { - subcache = - processAllAvailableMatches(data, { + let dataSubcache = cache.get(data); + if (!dataSubcache) { + cache.set(data, dataSubcache = new Map()); + } + + const fuzzHash = getFuzzHash(fuzz); + let fuzzSubcache = dataSubcache.get(fuzzHash); + if (!fuzzSubcache) { + dataSubcache.set(fuzzHash, fuzzSubcache = + processAllAvailableMatches(data, fuzz, { include, getMatchableNames, getMatchableDirectories, - }); - - cache.set(data, subcache); + })); } - const {byDirectory, byName, multipleNameMatches} = subcache; + const {byDirectory, byName, multipleNameMatches} = fuzzSubcache; return matchHelper(fullRef, mode, { matchByDirectory: @@ -233,7 +329,7 @@ function findHelper({ }), matchByName: - prepareMatchByName(mode, { + prepareMatchByName(mode, fuzz, { byName, multipleNameMatches, }), @@ -312,7 +408,7 @@ function findMixedHelper(config) { const multipleNameMatches = Object.create(null); for (const spec of specs) { - processAvailableMatchesByName(data, { + processAvailableMatchesByName(data, null, { ...spec, results: byName, @@ -345,11 +441,17 @@ function findMixedHelper(config) { }); } - return byDirectory[referenceType][directory]; + const match = byDirectory[referenceType][directory]; + + if (match) { + return match.thing; + } else { + return null; + } }, matchByName: - prepareMatchByName(mode, { + prepareMatchByName(mode, null, { byName, multipleNameMatches, }), diff --git a/src/gen-thumbs.js b/src/gen-thumbs.js index 97cf74a9..18f88ce6 100644 --- a/src/gen-thumbs.js +++ b/src/gen-thumbs.js @@ -162,10 +162,11 @@ import { import dimensionsOf from 'image-size'; -import CacheableObject from '#cacheable-object'; import {stringifyCache} from '#cli'; import {commandExists, isMain, promisifyProcess, traverse} from '#node-utils'; import {sortByName} from '#sort'; +import {delay, empty, filterMultipleArrays, queue, stitchArrays, unique} + from '#sugar'; import { colors, @@ -178,16 +179,6 @@ import { progressPromiseAll, } from '#cli'; -import { - delay, - empty, - chunkMultipleArrays, - filterMultipleArrays, - queue, - stitchArrays, - unique, -} from '#sugar'; - export const defaultMagickThreads = 8; function getSpecbustForCacheEntry(entry) { @@ -447,7 +438,7 @@ async function getImageMagickVersion(binary) { try { await promisifyProcess(proc, false); - } catch (error) { + } catch { return null; } @@ -556,42 +547,56 @@ async function determineThumbtacksNeededForFile({ return mismatchedWithinRightSize; } -async function generateImageThumbnail(imagePath, thumbtack, { +// Write all requested thumbtacks for a source image in one pass +// This saves a lot of disk reads which are probably the main bottleneck +function prepareConvertArgs(filePathInMedia, dirnameInCache, thumbtacks) { + const args = [filePathInMedia, '-strip']; + + const basename = + path.basename(filePathInMedia, path.extname(filePathInMedia)); + + // do larger sizes first + thumbtacks.sort((a, b) => thumbnailSpec[b].size - thumbnailSpec[a].size); + + for (const tack of thumbtacks) { + const {size, quality} = thumbnailSpec[tack]; + const filename = `${basename}.${tack}.jpg`; + const filePathInCache = path.join(dirnameInCache, filename); + args.push( + '(', '+clone', + '-resize', `${size}x${size}>`, + '-interlace', 'Plane', + '-quality', `${quality}%`, + '-write', filePathInCache, + '+delete', ')', + ); + } + + // throw away the (already written) image stream + args.push('null:'); + + return args; +} + +async function generateImageThumbnails(imagePath, thumbtacks, { mediaPath, mediaCachePath, spawnConvert, }) { + if (empty(thumbtacks)) return; + const filePathInMedia = path.join(mediaPath, imagePath); const dirnameInCache = path.join(mediaCachePath, path.dirname(imagePath)); - const filename = - path.basename(imagePath, path.extname(imagePath)) + - `.${thumbtack}.jpg`; - - const filePathInCache = - path.join(dirnameInCache, filename); - await mkdir(dirnameInCache, {recursive: true}); - const specEntry = thumbnailSpec[thumbtack]; - const {size, quality} = specEntry; - - const convertProcess = spawnConvert([ - filePathInMedia, - '-strip', - '-resize', - `${size}x${size}>`, - '-interlace', - 'Plane', - '-quality', - `${quality}%`, - filePathInCache, - ]); - - await promisifyProcess(convertProcess, false); + const convertArgs = + prepareConvertArgs(filePathInMedia, dirnameInCache, thumbtacks); + + await promisifyProcess(spawnConvert(convertArgs), false); } export async function determineMediaCachePath({ @@ -628,7 +633,7 @@ export async function determineMediaCachePath({ try { const files = await readdir(mediaPath); mediaIncludesThumbnailCache = files.includes(CACHE_FILE); - } catch (error) { + } catch { mediaIncludesThumbnailCache = false; } @@ -861,7 +866,7 @@ export async function migrateThumbsIntoDedicatedCacheDirectory({ path.join(mediaPath, CACHE_FILE), path.join(mediaCachePath, CACHE_FILE)); logInfo`Moved thumbnail cache file.`; - } catch (error) { + } catch { logWarn`Failed to move cache file. (${CACHE_FILE})`; logWarn`Check its permissions, or try copying/pasting.`; } @@ -1100,33 +1105,23 @@ export default async function genThumbs({ const writeMessageFn = () => `Writing image thumbnails. [failed: ${numFailed}]`; - const generateCallImageIndices = - imageThumbtacksNeeded - .flatMap(({length}, index) => - Array.from({length}, () => index)); - - const generateCallImagePaths = - generateCallImageIndices - .map(index => imagePaths[index]); - - const generateCallThumbtacks = - imageThumbtacksNeeded.flat(); - const generateCallFns = stitchArrays({ - imagePath: generateCallImagePaths, - thumbtack: generateCallThumbtacks, - }).map(({imagePath, thumbtack}) => () => - generateImageThumbnail(imagePath, thumbtack, { + imagePath: imagePaths, + thumbtacks: imageThumbtacksNeeded, + }).map(({imagePath, thumbtacks}) => () => + generateImageThumbnails(imagePath, thumbtacks, { mediaPath, mediaCachePath, spawnConvert, }).catch(error => { numFailed++; - return ({error}); + return {error}; })); - logInfo`Generating ${generateCallFns.length} thumbnails for ${imagePaths.length} media files.`; + const totalThumbs = imageThumbtacksNeeded.reduce((sum, tacks) => sum + tacks.length, 0); + + logInfo`Generating ${totalThumbs} thumbnails for ${imagePaths.length} media files.`; if (generateCallFns.length > 500) { logInfo`Go get a latte - this could take a while!`; } @@ -1135,37 +1130,30 @@ export default async function genThumbs({ await progressPromiseAll(writeMessageFn, queue(generateCallFns, magickThreads)); - let successfulIndices; + let successfulPaths; { - const erroredIndices = generateCallImageIndices.slice(); - const erroredPaths = generateCallImagePaths.slice(); - const erroredThumbtacks = generateCallThumbtacks.slice(); + const erroredPaths = imagePaths.slice(); const errors = generateCallResults.map(result => result?.error); const {removed} = filterMultipleArrays( - erroredIndices, erroredPaths, - erroredThumbtacks, errors, - (_index, _imagePath, _thumbtack, error) => error); + (_imagePath, error) => error); - successfulIndices = new Set(removed[0]); - - const chunks = - chunkMultipleArrays(erroredPaths, erroredThumbtacks, errors, - (imagePath, lastImagePath) => imagePath !== lastImagePath); + ([successfulPaths] = removed); // TODO: This should obviously be an aggregate error. // ...Just like every other error report here, and those dang aggregates // should be constructable from within the queue, rather than after. - for (const [[imagePath], thumbtacks, errors] of chunks) { - logError`Failed to generate thumbnails for ${imagePath}:`; - for (const {thumbtack, error} of stitchArrays({thumbtack: thumbtacks, error: errors})) { - logError`- ${thumbtack}: ${error}`; - } - } + stitchArrays({ + imagePath: erroredPaths, + error: errors, + }).forEach(({imagePath, error}) => { + logError`Failed to generate thumbnails for ${imagePath}:`; + logError`- ${error}`; + }); if (empty(errors)) { logInfo`All needed thumbnails generated successfully - nice!`; @@ -1179,8 +1167,8 @@ export default async function genThumbs({ imagePaths, imageThumbtacksNeeded, imageDimensions, - (_imagePath, _thumbtacksNeeded, _dimensions, index) => - successfulIndices.has(index)); + (imagePath, _thumbtacksNeeded, _dimensions) => + successfulPaths.includes(imagePath)); for (const { imagePath, @@ -1251,6 +1239,11 @@ export function getExpectedImagePaths(mediaPath, {urls, wikiData}) { .filter(part => part.asset) .map(part => fromRoot.to('media.albumWallpaperPart', album.directory, part.asset))), + + wikiData.wikiInfo.wikiWallpaperParts + .filter(part => part.asset) + .map(part => + fromRoot.to('media.path', part.asset)), ].flat(); sortByName(paths, {getName: path => path}); diff --git a/src/html.js b/src/html.js index 9e4c39ab..4cac9525 100644 --- a/src/html.js +++ b/src/html.js @@ -2,6 +2,8 @@ import {inspect} from 'node:util'; +import striptags from 'striptags'; + import {withAggregate} from '#aggregate'; import {colors} from '#cli'; import {empty, typeAppearance, unique} from '#sugar'; @@ -39,6 +41,40 @@ export const selfClosingTags = [ 'wbr', ]; +// Every element under: +// https://html.spec.whatwg.org/multipage/text-level-semantics.html +export const textLevelSemanticTags = [ + 'a', + 'abbr', + 'b', + 'bdi', + 'bdo', + 'br', + 'cite', + 'code', + 'data', + 'dfn', + 'em', + 'i', + 'kbd', + 'mark', + 'q', + 'rp', + 'rt', + 'ruby', + 's', + 'samp', + 'small', + 'span', + 'strong', + 'sub', + 'sup', + 'time', + 'u', + 'var', + 'wbr', +]; + // Not so comprehensive!! export const attributeSpec = { 'class': { @@ -53,6 +89,17 @@ export const attributeSpec = { }, }; +let disabledSlotValidation = false; +let disabledTagTracing = false; + +export function disableSlotValidation() { + disabledSlotValidation = true; +} + +export function disableTagTracing() { + disabledTagTracing = true; +} + // Pass to tag() as an attributes key to make tag() return a 8lank tag if the // provided content is empty. Useful for when you'll only 8e showing an element // according to the presence of content that would 8elong there. @@ -223,7 +270,11 @@ export function isBlank(content) { // could include content. These need to be checked too. // Check each of the templates one at a time. for (const template of result) { - const content = template.content; + // Resolve the content all the way down to a tag - + // if it's a template that returns another template, + // that won't do, because we need to detect if its + // final content is a tag marked onlyIfSiblings. + const content = normalize(template); if (content instanceof Tag && content.onlyIfSiblings) { continue; @@ -271,7 +322,11 @@ export const validators = { }, }; -export function blank() { +export function blank(...args) { + if (args.length) { + throw new Error(`Passed arguments - did you mean isBlank() instead?`) + } + return []; } @@ -340,6 +395,22 @@ export function normalize(content) { return Tag.normalize(content); } +export function escape(string, {attribute = false} = {}) { + // https://html.spec.whatwg.org/multipage/parsing.html#escapingString + + string = string + .replaceAll('&', '&') + .replaceAll('\u00a0', ' ') + .replaceAll('<', '<') + .replaceAll('>', '>'); + + if (attribute) { + string = string.replaceAll('"', '"'); + } + + return string; +} + export class Tag { #tagName = ''; #content = null; @@ -352,8 +423,10 @@ export class Tag { this.attributes = attributes; this.content = content; - this.#traceError = new Error(); - } + if (!disabledTagTracing) { + this.#traceError = new Error(); + } +} clone() { return Reflect.construct(this.constructor, [ @@ -432,6 +505,7 @@ export class Tag { this.#content = contentArray; this.#content.toString = () => this.#stringifyContent(); + this.#content.toPlainText = () => this.#plainifyContent(); } get content() { @@ -583,7 +657,7 @@ export class Tag { try { this.content = this.content; - } catch (error) { + } catch { this.#setAttributeFlag(imaginarySibling, false); } } @@ -640,6 +714,10 @@ export class Tag { : '\n')); } + toPlainText() { + return this.content.toPlainText(); + } + #getContentJoiner() { if (this.joinChildren === undefined) { return '\n'; @@ -659,11 +737,8 @@ export class Tag { const joiner = this.#getContentJoiner(); - let content = ''; let blockwrapClosers = ''; - let seenSiblingIndependentContent = false; - const chunkwrapSplitter = (this.chunkwrap ? this.#getAttributeRaw('split') @@ -674,108 +749,64 @@ export class Tag { ? false : null); - let contentItems; - - determineContentItems: { - if (this.chunkwrap) { - contentItems = smush(this).content; - break determineContentItems; - } - - contentItems = this.content; - } - - for (const [index, item] of contentItems.entries()) { - const nonTemplateItem = - Template.resolve(item); - - if (nonTemplateItem instanceof Tag && nonTemplateItem.imaginarySibling) { - seenSiblingIndependentContent = true; - continue; - } - - let itemContent; - try { - itemContent = nonTemplateItem.toString(); - } catch (caughtError) { - const indexPart = colors.yellow(`child #${index + 1}`); - - const error = - new Error( - `Error in ${indexPart} ` + - `of ${inspect(this, {compact: true})}`, - {cause: caughtError}); - - error[Symbol.for(`hsmusic.aggregate.alwaysTrace`)] = true; - error[Symbol.for(`hsmusic.aggregate.traceFrom`)] = this.#traceError; - - error[Symbol.for(`hsmusic.aggregate.unhelpfulTraceLines`)] = [ - /content-function\.js/, - /util\/html\.js/, - ]; - - error[Symbol.for(`hsmusic.aggregate.helpfulTraceLines`)] = [ - /content\/dependencies\/(.*\.js:.*(?=\)))/, - ]; - - throw error; - } - - if (!itemContent) { - continue; - } - - if (!(nonTemplateItem instanceof Tag) || !nonTemplateItem.onlyIfSiblings) { - seenSiblingIndependentContent = true; - } + const contentItems = + (this.chunkwrap + ? smush(this).content + : this.content); + + let content = this.#renderContentItems({ + from: '', + items: contentItems, + + getItemContent: item => item.toString(), + + appendItemContent(content, itemContent, item) { + const chunkwrapChunks = + (typeof item === 'string' && chunkwrapSplitter + ? Array.from(getChunkwrapChunks(itemContent, chunkwrapSplitter)) + : null); + + const itemIncludesChunkwrapSplit = + (chunkwrapChunks + ? chunkwrapChunks.length > 1 + : null); + + if (content) { + if (itemIncludesChunkwrapSplit && !seenChunkwrapSplitter) { + // The first time we see a chunkwrap splitter, backtrack and wrap + // the content *so far* in a chunk. This will be treated just like + // any other open chunkwrap, and closed after the first chunk of + // this item! (That means the existing content is part of the same + // chunk as the first chunk included in this content, which makes + // sense, because that first chink is really just more text that + // precedes the first split.) + content = `<span class="chunkwrap">` + content; + } - const chunkwrapChunks = - (typeof nonTemplateItem === 'string' && chunkwrapSplitter - ? Array.from(getChunkwrapChunks(itemContent, chunkwrapSplitter)) - : null); - - const itemIncludesChunkwrapSplit = - (chunkwrapChunks - ? chunkwrapChunks.length > 1 - : null); - - if (content) { - if (itemIncludesChunkwrapSplit && !seenChunkwrapSplitter) { - // The first time we see a chunkwrap splitter, backtrack and wrap - // the content *so far* in a chunk. This will be treated just like - // any other open chunkwrap, and closed after the first chunk of - // this item! (That means the existing content is part of the same - // chunk as the first chunk included in this content, which makes - // sense, because that first chink is really just more text that - // precedes the first split.) - content = `<span class="chunkwrap">` + content; + content += joiner; + } else if (itemIncludesChunkwrapSplit) { + // We've encountered a chunkwrap split before any other content. + // This means there's no content to wrap, no existing chunkwrap + // to close, and no reason to add a joiner, but we *do* need to + // enter a chunkwrap wrapper *now*, so the first chunk of this + // item will be properly wrapped. + content = `<span class="chunkwrap">`; } - content += joiner; - } else if (itemIncludesChunkwrapSplit) { - // We've encountered a chunkwrap split before any other content. - // This means there's no content to wrap, no existing chunkwrap - // to close, and no reason to add a joiner, but we *do* need to - // enter a chunkwrap wrapper *now*, so the first chunk of this - // item will be properly wrapped. - content = `<span class="chunkwrap">`; - } - - if (itemIncludesChunkwrapSplit) { - seenChunkwrapSplitter = true; - } + if (itemIncludesChunkwrapSplit) { + seenChunkwrapSplitter = true; + } - // Blockwraps only apply if they actually contain some content whose - // words should be kept together, so it's okay to put them beneath the - // itemContent check. They also never apply at the very start of content, - // because at that point there aren't any preceding words from which the - // blockwrap would differentiate its content. - if (nonTemplateItem instanceof Tag && nonTemplateItem.blockwrap && content) { - content += `<span class="blockwrap">`; - blockwrapClosers += `</span>`; - } + // Blockwraps only apply if they actually contain some content whose + // words should be kept together, so it's okay to put them beneath the + // itemContent check. They also never apply at the very start of content, + // because at that point there aren't any preceding words from which the + // blockwrap would differentiate its content. + if (item instanceof Tag && item.blockwrap && content) { + content += `<span class="blockwrap">`; + blockwrapClosers += `</span>`; + } - appendItemContent: { if (itemIncludesChunkwrapSplit) { for (const [index, {chunk, following}] of chunkwrapChunks.entries()) { if (index === 0) { @@ -809,17 +840,15 @@ export class Tag { } } - break appendItemContent; + return content; } - content += itemContent; - } - } + return content += itemContent; + }, + }); - // If we've only seen sibling-dependent content (or just no content), - // then the content in total is blank. - if (!seenSiblingIndependentContent) { - return ''; + if (!content.length) { + return content; } if (chunkwrapSplitter) { @@ -839,6 +868,130 @@ export class Tag { return content; } + #plainifyContent() { + // Doesn't play too nice with transformContent, because that function, + // working with the Marked library to process markdown, returns a mix of + // raw HTML strings and actual tags - this function only makes nice line + // breaks out of actual tags. + + if (this.selfClosing) { + return ''; + } + + let joiner = this.#getContentJoiner(); + + if (joiner instanceof Tag && joiner.tagName === 'br') { + joiner = '\n'; + } + + if (joiner === '\n') { + joiner = ' '; + } + + let content = this.#renderContentItems({ + from: '', + items: this.content, + + getItemContent: item => + (item instanceof Tag + ? item.toPlainText() + : item.toString()), + + appendItemContent(content, itemContent, item) { + if (joiner === ' ') { + if (item instanceof Tag && !textLevelSemanticTags.includes(item.tagName)) { + content += '\n\n'; + } else if (!content.endsWith(' ')) { + content += ' '; + } + } else { + content += joiner; + } + + return content += itemContent; + }, + }); + + content = + striptags(content) + .replaceAll(''', `'`) + .replaceAll('"', `"`); + + return content; + } + + #renderContentItems(config) { + let content = structuredClone(config.from); + + let seenSiblingIndependentContent = false; + + for (const [index, item] of config.items.entries()) { + const nonTemplateItem = Template.resolve(item); + + if (nonTemplateItem instanceof Tag && nonTemplateItem.imaginarySibling) { + seenSiblingIndependentContent = true; + continue; + } + + let itemContent; + try { + itemContent = config.getItemContent(nonTemplateItem); + } catch (caughtError) { + throw this.#annotateContentItemError(caughtError, index); + } + + if (!itemContent) { + continue; + } + + const previousLength = content.length; + + content = config.appendItemContent(content, itemContent, nonTemplateItem); + + if (content.length === previousLength) { + continue; + } + + if (!(nonTemplateItem instanceof Tag) || !nonTemplateItem.onlyIfSiblings) { + seenSiblingIndependentContent = true; + } + } + + // If we've only seen sibling-dependent content (or just no content), + // then the content in total is blank. + if (!seenSiblingIndependentContent) { + return config.from; + } + + return content; + } + + #annotateContentItemError(caughtError, index) { + const indexPart = colors.yellow(`child #${index + 1}`); + + const error = + new Error( + `Error in ${indexPart} ` + + `of ${inspect(this, {compact: true})}`, + {cause: caughtError}); + + if (this.#traceError && !disabledTagTracing) { + error[Symbol.for(`hsmusic.aggregate.alwaysTrace`)] = true; + error[Symbol.for(`hsmusic.aggregate.traceFrom`)] = this.#traceError; + + error[Symbol.for(`hsmusic.aggregate.unhelpfulTraceLines`)] = [ + /content-function\.js/, + /util\/html\.js/, + ]; + + error[Symbol.for(`hsmusic.aggregate.helpfulTraceLines`)] = [ + /content\/dependencies\/(.*\.js:.*(?=\)))/, + ]; + } + + return error; + } + static normalize(content) { // Normalizes contents that are valid from an `isHTML` perspective so // that it's always a pure, single Tag object. @@ -1131,6 +1284,34 @@ export class Attributes { } add(...args) { + // Very common case: add({class: 'foo', id: 'bar'})¡ + // The argument is a plain object (no Template, no Attributes, + // no blessAttributes symbol). We can skip the expensive + // isAttributesAdditionSinglet() validation and flatten/array handling. + if ( + args.length === 1 && + args[0] && + typeof args[0] === 'object' && + !Array.isArray(args[0]) && + !(args[0] instanceof Attributes) && + !(args[0] instanceof Template) && + !Object.hasOwn(args[0], blessAttributes) + ) { + const obj = args[0]; + + // Preserve existing merge semantics by funnelling each key through + // the internal #addOneAttribute helper (handles class/style union, + // unique merging, etc.) but avoid *per-object* validation overhead. + for (const key of Reflect.ownKeys(obj)) { + this.#addOneAttribute(key, obj[key]); + } + + // Match the original return style (list of results) so callers that + // inspect the return continue to work. + return obj; + } + + // Fall back to the original slow-but-thorough implementation switch (args.length) { case 1: isAttributesAdditionSinglet(args[0]); @@ -1142,10 +1323,11 @@ export class Attributes { default: throw new Error( - `Expected array or object, or attribute and value`); + 'Expected array or object, or attribute and value'); } } + with(...args) { const clone = this.clone(); clone.add(...args); @@ -1291,7 +1473,7 @@ export class Attributes { attributeKeyValues .map(([key, value]) => { const keyPart = key; - const escapedValue = this.#escapeAttributeValue(value); + const escapedValue = escape(value.toString(), {attribute: true}); const valuePart = (color ? colors.green(`"${escapedValue}"`) @@ -1367,13 +1549,6 @@ export class Attributes { } } - #escapeAttributeValue(value) { - return value - .toString() - .replaceAll('"', '"') - .replaceAll("'", '''); - } - static parse(string) { const attributes = Object.create(null); @@ -1473,6 +1648,8 @@ export function resolve(tagOrTemplate, { return Tag.normalize(tagOrTemplate); } else if (normalize === 'string') { return Tag.normalize(tagOrTemplate).toString(); + } else if (normalize === 'plain') { + return Tag.normalize(tagOrTemplate).toPlainText(); } else if (normalize) { throw new TypeError(`Expected normalize to be 'tag', 'string', or null`); } else { @@ -1521,6 +1698,61 @@ export function smooth(smoothie) { return tags(helper(smoothie)); } +export function inside(insidee) { + if (insidee instanceof Template) { + return inside(Template.resolve(insidee)); + } + + if (insidee instanceof Tag) { + return Array.from(smooth(tags(insidee.content)).content); + } + + return []; +} + +export function findInside(insidee, query) { + if (typeof query === 'object' && query.slots) { + return findInside(insidee, item => + Template.resolveForSlots(item, query.slots, 'null')); + } + + if (typeof query === 'object' && query.annotation) { + return findInside(insidee, item => + Template.resolveForAnnotation(item, query.annotation, 'null')); + } + + if (typeof query === 'object' && query.tag) { + return findInside(insidee, item => { + const tag = normalize(item); + if (tag.tagName === query) { + return tag; + } else { + return null; + } + }); + } + + if (typeof query === 'string') { + return findInside(insidee, item => + Template.resolveForContentFunction(item, query, 'null')); + } + + if (typeof query !== 'function') { + throw new Error(`Expected {slots}, {annotation}, or query function`); + } + + for (const item of inside(insidee)) { + const result = query(item); + if (result && result === true) { + return item; + } else if (result) { + return result; + } + } + + return null; +} + export function template(description) { return new Template(description); } @@ -1739,6 +1971,10 @@ export class Template { } static validateSlotValueAgainstDescription(value, description) { + if (disabledSlotValidation) { + return true; + } + if (value === undefined) { throw new TypeError(`Specify value as null or don't specify at all`); } @@ -1916,17 +2152,11 @@ export class Template { return this.content.toString(); } - static resolve(tagOrTemplate) { + static resolve(content) { // Flattens contents of a template, recursively "resolving" until a // non-template is ready (or just returns a provided non-template // argument as-is). - if (!(tagOrTemplate instanceof Template)) { - return tagOrTemplate; - } - - let {content} = tagOrTemplate; - while (content instanceof Template) { content = content.content; } @@ -1934,7 +2164,7 @@ export class Template { return content; } - static resolveForSlots(tagOrTemplate, slots) { + static resolveForSlots(content, slots, without = 'throw') { if (!slots || typeof slots !== 'object') { throw new Error( `Expected slots to be an object or array, ` + @@ -1942,24 +2172,87 @@ export class Template { } if (!Array.isArray(slots)) { - return Template.resolveForSlots(tagOrTemplate, Object.keys(slots)).slots(slots); + return Template.resolveForSlots(content, Object.keys(slots)).slots(slots); } - while (tagOrTemplate && tagOrTemplate instanceof Template) { + while (content instanceof Template) { try { for (const slot of slots) { - tagOrTemplate.getSlotDescription(slot); + content.getSlotDescription(slot); } - return tagOrTemplate; + return content; } catch { - tagOrTemplate = tagOrTemplate.content; + content = content.content; } } - throw new Error( - `Didn't find slots ${inspect(slots, {compact: true})} ` + - `resolving ${inspect(tagOrTemplate, {compact: true})}`); + if (without === 'throw') { + throw new Error( + `Didn't find slots ${inspect(slots, {compact: true})} ` + + `resolving ${inspect(content, {compact: true})}`); + } else { + return null; + } + } + + static resolveForAnnotation(content, annotation, without = 'throw') { + if (!annotation || typeof annotation !== 'string') { + throw new Error( + `Expected annotation to be a string, ` + + `got ${typeAppearance(annotation)}`); + } + + while (content instanceof Template) { + if (content.description.annotation === annotation) { + return content; + } else { + content = content.content; + } + } + + if (without === 'throw') { + throw new Error( + `Didn't find annotation ${inspect(annotation, {compact: true})} ` + + `resolving ${inspect(content, {compact: true})}`); + } else { + return null; + } + } + + static resolveForContentFunction(content, dependency, without = 'throw') { + if (!dependency || typeof dependency !== 'string') { + throw new Error( + `Expected dependency to be a string, ` + + `got ${typeAppearance(dependency)}`); + } + + const considerContentFunction = () => + (content instanceof Tag || content instanceof Template) && + Object.hasOwn(content, Symbol.for('hsmusic.contentFunction.via')) && + content[Symbol.for('hsmusic.contentFunction.via')].includes(dependency); + + while (content instanceof Template) { + if (considerContentFunction()) { + return content; + } else if (content.description.annotation === dependency) { + return content; + } else { + content = content.content; + } + } + + if (considerContentFunction()) { + return content; + } + + if (without === 'throw') { + throw new Error( + `Didn't find dependency ${inspect(dependency, {compact: true})} ` + + `resolving ${inspect(content, {compact: true})}`); + } else { + return null; + } } [inspect.custom]() { diff --git a/src/listing-spec.js b/src/listing-spec.js index 142c5976..a301845b 100644 --- a/src/listing-spec.js +++ b/src/listing-spec.js @@ -178,6 +178,7 @@ listingSpec.push({ directory: 'tracks/with-lyrics', stringsKey: 'listTracks.withLyrics', contentFunction: 'listTracksWithLyrics', + seeAlso: ['tracks/needing-lyrics'], }); listingSpec.push({ @@ -195,6 +196,13 @@ listingSpec.push({ }); listingSpec.push({ + directory: 'tracks/needing-lyrics', + stringsKey: 'listTracks.needingLyrics', + contentFunction: 'listTracksNeedingLyrics', + seeAlso: ['tracks/with-lyrics'], +}); + +listingSpec.push({ directory: 'tags/by-name', stringsKey: 'listArtTags.byName', contentFunction: 'listArtTagsByName', @@ -291,7 +299,7 @@ for (const [index, listing] of listingSpec.entries()) { errors.push(new AggregateError(suberrors, `Errors matching "see also" listings for ${listing.directory}`)); } } else { - listing.seeAlso = null; + listing.seeAlso = []; } } diff --git a/src/page/album.js b/src/page/album.js index 696e2854..e585618c 100644 --- a/src/page/album.js +++ b/src/page/album.js @@ -8,15 +8,22 @@ export function targets({wikiData}) { export function pathsForTarget(album) { return [ - { - type: 'page', - path: ['album', album.directory], - - contentFunction: { - name: 'generateAlbumInfoPage', - args: [album], - }, - }, + (album.style === 'single' + ? { + type: 'redirect', + fromPath: ['album', album.directory], + toPath: ['track', album.tracks[0].directory], + title: album.name, + } + : { + type: 'page', + path: ['album', album.directory], + + contentFunction: { + name: 'generateAlbumInfoPage', + args: [album], + }, + }), { type: 'page', diff --git a/src/page/artist.js b/src/page/artist.js index 257e060d..7cd50bb3 100644 --- a/src/page/artist.js +++ b/src/page/artist.js @@ -32,6 +32,23 @@ export function pathsForTarget(artist) { args: [artist], }, }, + + { + type: 'page', + path: ['artistRollingWindow', artist.directory], + + condition: () => + artist.musicContributions.some(contrib => contrib.date) || + artist.artworkContributions.some(contrib => + contrib.date && + contrib.thingProperty !== 'wallpaperArtistContribs' && + contrib.thingProperty !== 'bannerArtistContribs'), + + contentFunction: { + name: 'generateArtistRollingWindowPage', + args: [artist], + }, + }, ]; } diff --git a/src/replacer.js b/src/replacer.js index 32657a5a..8a929444 100644 --- a/src/replacer.js +++ b/src/replacer.js @@ -8,7 +8,8 @@ import * as marked from 'marked'; import * as html from '#html'; -import {escapeRegex, typeAppearance} from '#sugar'; +import {empty, escapeRegex, typeAppearance} from '#sugar'; +import {matchInlineLinks, matchMarkdownLinks} from '#wiki-data'; export const replacerSpec = { 'album': { @@ -174,6 +175,11 @@ export const replacerSpec = { find: 'trackWithArtwork', link: 'linkTrackReferencingArtworks', }, + + 'tooltip': { + value: (ref) => ref, + link: null, + } }; // Syntax literals. @@ -184,6 +190,9 @@ const tagHash = '#'; const tagArgument = '*'; const tagArgumentValue = '='; const tagLabel = '|'; +const tooltipBeginning = '<<'; +const tooltipEnding = '>>'; +const tooltipContent = ':'; const noPrecedingWhitespace = '(?<!\\s)'; @@ -202,6 +211,14 @@ const R_tagArgumentValue = escapeRegex(tagArgumentValue); const R_tagLabel = escapeRegex(tagLabel); +const R_tooltipBeginning = + '(?<=[^<]|^)' + escapeRegex(tooltipBeginning) + '(?!<)'; + +const R_tooltipEnding = + '(?<=[^>]|^)' + escapeRegex(tooltipEnding) + '(?!>)'; + +const R_tooltipContent = escapeRegex(tooltipContent); + const regexpCache = {}; const makeError = (i, message) => ({i, type: 'error', data: {message}}); @@ -241,9 +258,13 @@ function parseNodes(input, i, stopAt, textOnly) { } }; - const literalsToMatch = stopAt - ? stopAt.concat([R_tagBeginning]) - : [R_tagBeginning]; + const beginnings = [tagBeginning, tooltipBeginning]; + const R_beginnings = [R_tagBeginning, R_tooltipBeginning]; + + const literalsToMatch = + (stopAt + ? stopAt.concat(R_beginnings) + : R_beginnings); // The 8ackslash stuff here is to only match an even (or zero) num8er // of sequential 'slashes. Even amounts always cancel out! Odd amounts @@ -294,8 +315,10 @@ function parseNodes(input, i, stopAt, textOnly) { if (textOnly && closestMatch === tagBeginning) throw makeError(i, `Unexpected [[tag]] - expected only text here.`); + if (textOnly && closestMatch === tooltipBeginning) + throw makeError(i, `Unexpected <<tooltip>> - expected only text here.`); - const stopHere = closestMatch !== tagBeginning; + const stopHere = !beginnings.includes(closestMatch); iString = i; i = closestMatchIndex; @@ -447,6 +470,51 @@ function parseNodes(input, i, stopAt, textOnly) { continue; } + + if (closestMatch === tooltipBeginning) { + const iTooltip = closestMatchIndex; + + let N; + + // Label (hoverable text) + + let label; + + N = parseNodes(input, i, [R_tooltipContent, R_tooltipEnding]); + + if (!stopped) + throw endOfInput(i, `reading tooltip label`); + if (input.slice(i).startsWith(tooltipEnding)) + throw makeError(i, `Expected tooltip label and content.`); + if (!N.length) + throw makeError(i, `Expected tooltip label before content.`); + + label = N; + i = stop_iParse; + + // Content (tooltip text) + + let content; + + N = parseNodes(input, i, [R_tooltipEnding]); + + if (!stopped) + throw endOfInput(i, `reading tooltip content`); + if (!N.length) + throw makeError(i, `Expected tooltip content`); + + content = N; + i = stop_iParse; + + nodes.push({ + i: iTooltip, + iEnd: i, + type: 'tooltip', + data: {label, content}, + }); + + continue; + } } return nodes; @@ -458,7 +526,7 @@ export function squashBackslashes(text) { // a set of characters where the backslash carries meaning // into later formatting (i.e. markdown). Note that we do // NOT compress double backslashes into single backslashes. - return text.replace(/([^\\](?:\\{2})*)\\(?![\\*_~>-])/g, '$1'); + return text.replace(/([^\\](?:\\{2})*)\\(?![\\*_~>.-])/g, '$1'); } export function restoreRawHTMLTags(text) { @@ -520,6 +588,7 @@ export function postprocessComments(inputNodes) { function postprocessHTMLTags(inputNodes, tagName, callback) { const outputNodes = []; + const errors = []; const lastNode = inputNodes.at(-1); @@ -587,10 +656,16 @@ function postprocessHTMLTags(inputNodes, tagName, callback) { return false; })(); - outputNodes.push( - callback(attributes, { - inline, - })); + try { + outputNodes.push( + callback(attributes, { + inline, + })); + } catch (caughtError) { + errors.push(new Error( + `Failed to process ${match[0]}`, + {cause: caughtError})); + } // No longer at the start of a line after the tag - there will at // least be text with only '\n' before the next of this tag that's @@ -613,15 +688,33 @@ function postprocessHTMLTags(inputNodes, tagName, callback) { outputNodes.push(node); } + if (!empty(errors)) { + throw new AggregateError( + errors, + `Errors postprocessing <${tagName}> tags`); + } + return outputNodes; } +function complainAboutMediaSrc(src) { + if (!src) { + throw new Error(`Missing "src" attribute`); + } + + if (src.startsWith('/media/')) { + throw new Error(`Start "src" with "media/", not "/media/"`); + } +} + export function postprocessImages(inputNodes) { return postprocessHTMLTags(inputNodes, 'img', (attributes, {inline}) => { const node = {type: 'image'}; node.src = attributes.get('src'); + complainAboutMediaSrc(node.src); + node.inline = attributes.get('inline') ?? inline; if (attributes.get('link')) node.link = attributes.get('link'); @@ -642,13 +735,17 @@ export function postprocessImages(inputNodes) { export function postprocessVideos(inputNodes) { return postprocessHTMLTags(inputNodes, 'video', - attributes => { + (attributes, {inline}) => { const node = {type: 'video'}; node.src = attributes.get('src'); + complainAboutMediaSrc(node.src); + + node.inline = attributes.get('inline') ?? inline; if (attributes.get('width')) node.width = parseInt(attributes.get('width')); if (attributes.get('height')) node.height = parseInt(attributes.get('height')); + if (attributes.get('align')) node.align = attributes.get('align'); if (attributes.get('pixelate')) node.pixelate = true; return node; @@ -661,8 +758,13 @@ export function postprocessAudios(inputNodes) { const node = {type: 'audio'}; node.src = attributes.get('src'); + complainAboutMediaSrc(node.src); + node.inline = attributes.get('inline') ?? inline; + if (attributes.get('align')) node.align = attributes.get('align'); + if (attributes.get('nameless')) node.nameless = true; + return node; }); } @@ -754,7 +856,7 @@ export function postprocessSummaries(inputNodes) { } export function postprocessExternalLinks(inputNodes) { - const outputNodes = []; + let outputNodes = []; for (const node of inputNodes) { if (node.type !== 'text') { @@ -762,110 +864,292 @@ export function postprocessExternalLinks(inputNodes) { continue; } - const plausibleLinkRegexp = /\[.*?\)/g; + let textNode = { + i: node.i, + iEnd: null, + type: 'text', + data: '', + }; - let textContent = ''; + let parseFrom = 0; + for (const match of matchMarkdownLinks(node.data, {marked})) { + const {label, href, index, length} = match; - let plausibleMatch = null, parseFrom = 0; - while (plausibleMatch = plausibleLinkRegexp.exec(node.data)) { - textContent += node.data.slice(parseFrom, plausibleMatch.index); - - // Pedantic rules use more particular parentheses detection in link - // destinations - they allow one level of balanced parentheses, and - // otherwise, parentheses must be escaped. This allows for entire links - // to be wrapped in parentheses, e.g below: - // - // This is so cool. ([You know??](https://example.com)) - // - const definiteMatch = - marked.Lexer.rules.inline.pedantic.link - .exec(node.data.slice(plausibleMatch.index)); - - if (definiteMatch) { - const {1: label, 2: href} = definiteMatch; - - // Split the containing text node into two - the second of these will - // be added after iterating over matches, or by the next match. - if (textContent.length) { - outputNodes.push({type: 'text', data: textContent}); - textContent = ''; - } + textNode.data += node.data.slice(parseFrom, index); - const offset = plausibleMatch.index + definiteMatch.index; - const length = definiteMatch[0].length; + // Split the containing text node into two - the second of these will + // be filled in and pushed by the next match, or after iterating over + // all matches. + if (textNode.data) { + textNode.iEnd = textNode.i + textNode.data.length; + outputNodes.push(textNode); - outputNodes.push({ - i: node.i + offset, - iEnd: node.i + offset + length, - type: 'external-link', - data: {label, href}, - }); + textNode = { + i: node.i + index + length, + iEnd: null, + type: 'text', + data: '', + }; + } - parseFrom = offset + length; - } else { - parseFrom = plausibleMatch.index; + outputNodes.push({ + i: node.i + index, + iEnd: node.i + index + length, + type: 'external-link', + data: {label, href}, + }); + + parseFrom = index + length; + } + + if (parseFrom !== node.data.length) { + textNode.data += node.data.slice(parseFrom); + textNode.iEnd = node.iEnd; + } + + if (textNode.data) { + outputNodes.push(textNode); + } + } + + // Repeat everything, but for inline links, which are just a URL on its own, + // not formatted as a Markdown link. These don't have provided labels, and + // get labels automatically filled in by content code. + + inputNodes = outputNodes; + outputNodes = []; + + for (const node of inputNodes) { + if (node.type !== 'text') { + outputNodes.push(node); + continue; + } + + let textNode = { + i: node.i, + iEnd: null, + type: 'text', + data: '', + }; + + let parseFrom = 0; + for (const match of matchInlineLinks(node.data)) { + const {href, index, length} = match; + + textNode.data += node.data.slice(parseFrom, index); + + if (textNode.data) { + textNode.iEnd = textNode.i + textNode.data.length; + outputNodes.push(textNode); + + textNode = { + i: node.i + index + length, + iEnd: null, + type: 'text', + data: '', + }; } + + outputNodes.push({ + i: node.i + index, + iEnd: node.i + index + length, + type: 'external-link', + data: {label: null, href}, + }); + + parseFrom = index + length; } if (parseFrom !== node.data.length) { - textContent += node.data.slice(parseFrom); + textNode.data += node.data.slice(parseFrom); + textNode.iEnd = node.iEnd; } - if (textContent.length) { - outputNodes.push({type: 'text', data: textContent}); + if (textNode.data) { + outputNodes.push(textNode); } } return outputNodes; } -export function parseInput(input) { +export function parseContentNodes(input, { + errorMode = 'throw', +} = {}) { if (typeof input !== 'string') { throw new TypeError(`Expected input to be string, got ${typeAppearance(input)}`); } - try { - let output = parseNodes(input, 0); - output = postprocessComments(output); - output = postprocessImages(output); - output = postprocessVideos(output); - output = postprocessAudios(output); - output = postprocessHeadings(output); - output = postprocessSummaries(output); - output = postprocessExternalLinks(output); - return output; - } catch (errorNode) { - if (errorNode.type !== 'error') { - throw errorNode; - } - - const { - i, - data: {message}, - } = errorNode; - - let lineStart = input.slice(0, i).lastIndexOf('\n'); - if (lineStart >= 0) { - lineStart += 1; - } else { - lineStart = 0; + let result = null, error = null; + + process: { + try { + result = parseNodes(input, 0); + } catch (caughtError) { + if (caughtError.type === 'error') { + const {i, data: {message}} = caughtError; + + let lineStart = input.slice(0, i).lastIndexOf('\n'); + if (lineStart >= 0) { + lineStart += 1; + } else { + lineStart = 0; + } + + let lineEnd = input.slice(i).indexOf('\n'); + if (lineEnd >= 0) { + lineEnd += i; + } else { + lineEnd = input.length; + } + + const line = input.slice(lineStart, lineEnd); + + const cursor = i - lineStart; + + error = + new SyntaxError( + `Parse error (at pos ${i}): ${message}\n` + + line + `\n` + + '-'.repeat(cursor) + '^'); + } else { + error = caughtError; + } + + // A parse error means there's no output to continue with at all, + // so stop here. + break process; } - let lineEnd = input.slice(i).indexOf('\n'); - if (lineEnd >= 0) { - lineEnd += i; + const postprocessErrors = []; + + for (const postprocess of [ + postprocessComments, + postprocessImages, + postprocessVideos, + postprocessAudios, + postprocessHeadings, + postprocessSummaries, + postprocessExternalLinks, + ]) { + try { + result = postprocess(result); + } catch (caughtError) { + const error = + new Error( + `Error in step ${`"${postprocess.name}"`}`, + {cause: caughtError}); + + error[Symbol.for('hsmusic.aggregate.translucent')] = true; + + postprocessErrors.push(error); + } + } + + if (!empty(postprocessErrors)) { + error = + new AggregateError( + postprocessErrors, + `Errors postprocessing content text`); + + error[Symbol.for('hsmusic.aggregate.translucent')] = 'single'; + } + } + + if (errorMode === 'throw') { + if (error) { + throw error; } else { - lineEnd = input.length; + return result; + } + } else if (errorMode === 'return') { + if (!result) { + result = [{ + i: 0, + iEnd: input.length, + type: 'text', + data: input, + }]; + } + + return {error, result}; + } else { + throw new Error(`Unknown errorMode ${errorMode}`); + } +} + +export function* splitContentNodesAround(nodes, splitter) { + if (splitter instanceof RegExp) { + const regex = splitter; + + splitter = function*(text) { + for (const match of text.matchAll(regex)) { + yield { + index: match.index, + length: match[0].length, + }; + } + }; + } + + if (typeof splitter === 'string') { + throw new TypeError(`Expected generator or regular expression`); + } + + function* splitTextNode(node) { + let textNode = { + i: node.i, + iEnd: null, + type: 'text', + data: '', + }; + + let parseFrom = 0; + for (const match of splitter(node.data)) { + const {index, length} = match; + + textNode.data += node.data.slice(parseFrom, index); + + if (textNode.data) { + textNode.iEnd = textNode.i + textNode.data.length; + yield textNode; + } + + yield { + i: node.i + index, + iEnd: node.i + index + length, + type: 'separator', + data: { + text: node.data.slice(index, index + length), + match, + }, + }; + + textNode = { + i: node.i + index + length, + iEnd: null, + type: 'text', + data: '', + }; + + parseFrom = index + length; } - const line = input.slice(lineStart, lineEnd); + if (parseFrom !== node.data.length) { + textNode.data += node.data.slice(parseFrom); + textNode.iEnd = node.iEnd; + } - const cursor = i - lineStart; + if (textNode.data) { + yield textNode; + } + } - throw new SyntaxError([ - `Parse error (at pos ${i}): ${message}`, - line, - '-'.repeat(cursor) + '^', - ].join('\n')); + for (const node of nodes) { + if (node.type === 'text') { + yield* splitTextNode(node); + } else { + yield node; + } } } diff --git a/src/search-select.js b/src/search-select.js new file mode 100644 index 00000000..e9048a9e --- /dev/null +++ b/src/search-select.js @@ -0,0 +1,217 @@ +// Complements the specs in search-shape.js with the functions that actually +// process live wiki data into records that are appropriate for storage. +// These files totally go together, so read them side by side, okay? + +import baseSearchSpec from '#search-shape'; +import {getKebabCase} from '#wiki-data'; + +function prepareArtwork(artwork, thing, { + checkIfImagePathHasCachedThumbnails, + getThumbnailEqualOrSmaller, + urls, +}) { + if (!artwork) { + return undefined; + } + + const hasWarnings = + artwork.artTags?.some(artTag => artTag.isContentWarning); + + const artworkPath = + artwork.path; + + if (!artworkPath) { + return undefined; + } + + const mediaSrc = + urls + .from('media.root') + .to(...artworkPath); + + if (!checkIfImagePathHasCachedThumbnails(mediaSrc)) { + return undefined; + } + + const selectedSize = + getThumbnailEqualOrSmaller( + (hasWarnings ? 'mini' : 'adorb'), + mediaSrc); + + const mediaSrcJpeg = + mediaSrc.replace(/\.(png|jpg)$/, `.${selectedSize}.jpg`); + + const displaySrc = + urls + .from('thumb.root') + .to('thumb.path', mediaSrcJpeg); + + const serializeSrc = + displaySrc.replace(thing.directory, '<>'); + + return serializeSrc; +} + +function baselineProcess(thing, _opts) { + const fields = {}; + + fields.primaryName = + thing.name; + + fields.artwork = + null; + + fields.color = + thing.color; + + fields.disambiguator = + null; + + return fields; +} + +function genericSelect(wikiData) { + const groupOrder = + wikiData.wikiInfo.divideTrackListsByGroups; + + const getGroupRank = thing => { + const relevantRanks = + Array.from(groupOrder.entries()) + .filter(({1: group}) => thing.groups.includes(group)) + .map(({0: index}) => index); + + if (relevantRanks.length === 0) { + return Infinity; + } else if (relevantRanks.length === 1) { + return relevantRanks[0]; + } else { + return relevantRanks[0] + 0.5; + } + } + + const sortByGroupRank = things => + things.sort((a, b) => getGroupRank(a) - getGroupRank(b)); + + return [ + sortByGroupRank(wikiData.albumData.slice()), + + wikiData.artTagData, + + wikiData.artistData + .filter(artist => !artist.isAlias), + + wikiData.flashData, + + wikiData.groupData, + + sortByGroupRank( + wikiData.trackData + .filter(track => + track.isMainRelease || + (getKebabCase(track.name) !== + getKebabCase(track.mainReleaseTrack.name)))), + ].flat(); +} + +function genericProcess(thing, opts) { + const fields = baselineProcess(thing, opts); + + const kind = + thing.constructor[Symbol.for('Thing.referenceType')]; + + const boundPrepareArtwork = artwork => + prepareArtwork(artwork, thing, opts); + + fields.artwork = + (kind === 'track' && thing.hasUniqueCoverArt + ? boundPrepareArtwork(thing.trackArtworks[0]) + : kind === 'track' + ? boundPrepareArtwork(thing.album.coverArtworks[0]) + : kind === 'album' + ? boundPrepareArtwork(thing.coverArtworks[0]) + : kind === 'flash' + ? boundPrepareArtwork(thing.coverArtwork) + : null); + + fields.parentName = + (kind === 'track' + ? thing.album.name + : kind === 'group' + ? thing.category.name + : kind === 'flash' + ? thing.act.name + : null); + + fields.disambiguator = + fields.parentName; + + fields.artTags = + (Array.from(new Set( + (kind === 'track' + ? thing.trackArtworks.flatMap(artwork => artwork.artTags) + : kind === 'album' + ? thing.coverArtworks.flatMap(artwork => artwork.artTags) + : [])))) + + .map(artTag => artTag.nameShort); + + fields.additionalNames = + (thing.constructor.hasPropertyDescriptor('additionalNames') + ? thing.additionalNames.map(entry => entry.name) + : thing.constructor.hasPropertyDescriptor('aliasNames') + ? thing.aliasNames + : []); + + const contribKeys = [ + 'artistContribs', + 'contributorContribs', + ]; + + const contributions = + contribKeys + .filter(key => Object.hasOwn(thing, key)) + .flatMap(key => thing[key]); + + fields.contributors = + contributions + .flatMap(({artist}) => [ + artist.name, + ...artist.aliasNames, + ]); + + const groups = + (Object.hasOwn(thing, 'groups') + ? thing.groups + : Object.hasOwn(thing, 'album') + ? thing.album.groups + : []); + + const mainContributorNames = + contributions + .map(({artist}) => artist.name); + + fields.groups = + groups + .filter(group => !mainContributorNames.includes(group.name)) + .map(group => group.name); + + return fields; +} + +const spiffySearchSpec = { + generic: { + ...baseSearchSpec.generic, + + select: genericSelect, + process: genericProcess, + }, + + verbatim: { + ...baseSearchSpec.verbatim, + + select: genericSelect, + process: genericProcess, + }, +}; + +export default spiffySearchSpec; diff --git a/src/search.js b/src/search.js index a2dae9e1..138a2d2c 100644 --- a/src/search.js +++ b/src/search.js @@ -9,11 +9,53 @@ import FlexSearch from 'flexsearch'; import {pack} from 'msgpackr'; import {logWarn} from '#cli'; -import {makeSearchIndex, populateSearchIndex, searchSpec} from '#search-spec'; +import {makeSearchIndex} from '#search-shape'; +import searchSpec from '#search-select'; import {stitchArrays} from '#sugar'; import {checkIfImagePathHasCachedThumbnails, getThumbnailEqualOrSmaller} from '#thumbs'; +// TODO: This function basically mirrors bind-utilities.js, which isn't +// exactly robust, but... binding might need some more thought across the +// codebase in *general.* +function bindSearchUtilities({ + checkIfImagePathHasCachedThumbnails, + getThumbnailEqualOrSmaller, + thumbsCache, + urls, +}) { + const bound = { + urls, + }; + + bound.checkIfImagePathHasCachedThumbnails = + (imagePath) => + checkIfImagePathHasCachedThumbnails(imagePath, thumbsCache); + + bound.getThumbnailEqualOrSmaller = + (preferred, imagePath) => + getThumbnailEqualOrSmaller(preferred, imagePath, thumbsCache); + + return bound; +} + +function populateSearchIndex(index, descriptor, wikiData, utilities) { + for (const thing of descriptor.select(wikiData)) { + const reference = thing.constructor.getReference(thing); + + let processed; + try { + processed = descriptor.process(thing, utilities); + } catch (caughtError) { + throw new Error( + `Failed to process searchable thing ${reference}`, + {cause: caughtError}); + } + + index.add({reference, ...processed}); + } +} + async function serializeIndex(index) { const results = {}; @@ -60,17 +102,20 @@ export async function writeSearchData({ .map(descriptor => makeSearchIndex(descriptor, {FlexSearch})); + const utilities = + bindSearchUtilities({ + checkIfImagePathHasCachedThumbnails, + getThumbnailEqualOrSmaller, + thumbsCache, + urls, + wikiData, + }); + stitchArrays({ index: indexes, descriptor: descriptors, }).forEach(({index, descriptor}) => - populateSearchIndex(index, descriptor, { - checkIfImagePathHasCachedThumbnails, - getThumbnailEqualOrSmaller, - thumbsCache, - urls, - wikiData, - })); + populateSearchIndex(index, descriptor, wikiData, utilities)); const serializedIndexes = await Promise.all(indexes.map(serializeIndex)); diff --git a/src/static/css/site.css b/src/static/css/site.css index 962dc896..61803c9d 100644 --- a/src/static/css/site.css +++ b/src/static/css/site.css @@ -61,7 +61,7 @@ body::before, .wallpaper-part { #page-container { max-width: 1100px; - margin: 0 auto 40px; + margin: 0 auto 38px; padding: 15px 0; } @@ -76,10 +76,25 @@ body::before, .wallpaper-part { height: unset; } +@property --banner-shine { + syntax: '<percentage>'; + initial-value: 0%; + inherits: false; +} + #banner { margin: 10px 0; width: 100%; position: relative; + + --banner-shine: 4%; + -webkit-box-reflect: below -12px linear-gradient(transparent, color-mix(in srgb, transparent, var(--banner-shine) white)); + transition: --banner-shine 0.8s; +} + +#banner:hover { + --banner-shine: 35%; + transition-delay: 0.3s; } #banner::after { @@ -261,7 +276,11 @@ body::before, .wallpaper-part { #page-container { background-color: var(--bg-color, rgba(35, 35, 35, 0.8)); color: #ffffff; - box-shadow: 0 0 40px rgba(0, 0, 0, 0.5); + border-bottom: 2px solid #fff1; + box-shadow: + 0 0 40px #0008, + 0 2px 15px -3px #2221, + 0 2px 6px 2px #1113; } #skippers > * { @@ -899,6 +918,11 @@ summary.underline-white > span:hover a:not(:hover) { display: inline-block; } +.wiki-search-result-disambiguator { + opacity: 0.9; + display: inline-block; +} + .wiki-search-result-image-container { align-self: flex-start; flex-shrink: 0; @@ -1013,10 +1037,13 @@ a .normal-content { } .image-media-link::after { - content: ''; - display: inline-block; - width: 22px; - height: 1em; + /* Thanks to Jay Freestone for being awesome: + * https://www.jayfreestone.com/writing/wrapping-and-inline-pseudo-elements/ + */ + + pointer-events: none; + content: '\200b'; + padding-left: 22px; background-color: var(--primary-color); @@ -1027,7 +1054,6 @@ a .normal-content { mask-repeat: no-repeat; mask-position: calc(100% - 2px); - vertical-align: text-bottom; } .image-media-link:hover::after { @@ -1095,7 +1121,16 @@ a .normal-content { font-weight: 800; } +.dot-switcher > span { + color: #ffffffcc; +} + .dot-switcher > span.current { + font-weight: normal; + color: white; +} + +.dot-switcher > span.current a { font-weight: 800; } @@ -1109,6 +1144,15 @@ a .normal-content { text-decoration: none !important; } +label:hover span { + text-decoration: underline; + text-decoration-style: solid; +} + +label > input[type=checkbox]:not(:checked) + span { + opacity: 0.8; +} + #secondary-nav { text-align: center; @@ -1193,7 +1237,12 @@ a .normal-content { white-space: nowrap; } -.isolate-tooltip-z-indexing > * { +:where(.isolate-tooltip-z-indexing) { + position: relative; + z-index: 1; +} + +:where(.isolate-tooltip-z-indexing > *) { position: relative; z-index: -1; } @@ -1246,7 +1295,8 @@ li:not(:first-child:last-child) .tooltip:where(:not(.cover-artwork .tooltip)), .missing-duration-tooltip, .commentary-date-tooltip, .rerelease-tooltip, -.first-release-tooltip { +.first-release-tooltip, +.content-tooltip { padding: 3px 4px 2px 2px; left: -10px; } @@ -1295,6 +1345,16 @@ li:not(:first-child:last-child) .tooltip:where(:not(.cover-artwork .tooltip)), height: 1.4em; } +.contribution-tooltip .chronology-heading { + grid-column-start: handle-start; + grid-column-end: platform-end; + min-width: 30ch; + + font-size: 0.85em; + font-style: oblique; + margin-bottom: 2px; +} + .contribution-tooltip .chronology-link { display: grid; grid-column-start: icon-start; @@ -1428,6 +1488,36 @@ li:not(:first-child:last-child) .tooltip:where(:not(.cover-artwork .tooltip)), font-size: 0.9em; } +.content-tooltip-guy .hoverable a { + text-decoration-color: transparent; + text-decoration-style: dotted; +} + +.content-tooltip-guy { + display: inline-block; +} + +.content-tooltip-guy:not(.has-link) .hoverable { + cursor: default; +} + +.content-tooltip-guy.has-link .text-with-tooltip-interaction-cue { + text-decoration-color: var(--primary-color); +} + +.content-tooltip .tooltip-content { + padding: 3px 4.5px; + width: max-content; + max-width: 240px; +} + +.cover-artwork .content-tooltip { + font-size: 0.85rem; + padding: 2px 3px; + width: max-content; + max-width: 220px; +} + .external-icon { display: inline-block; padding: 0 3px; @@ -1458,6 +1548,12 @@ li:not(:first-child:last-child) .tooltip:where(:not(.cover-artwork .tooltip)), color: var(--page-primary-color); } +.wiki-commentary s:not(.spoiler) { + text-decoration-color: #fff9; + text-decoration-thickness: 1.4px; + color: #fffb; +} + s.spoiler { display: inline-block; color: transparent; @@ -1478,6 +1574,42 @@ s.spoiler::-moz-selection { background: white; } +span.path, code.filename { + font-size: 0.95em; + font-family: "courier new", monospace; + font-weight: 800; + background: #ccc3; + + padding: 0.05em 0.5ch; + border: 1px solid #ccce; + border-radius: 2px; + line-height: 1.4; +} + +blockquote :is(span.path, code.filename) { + font-size: 0.9em; +} + +.image-details code.filename { + margin-left: -0.4ch; + opacity: 0.8; +} + +.image-details code.filename:hover { + opacity: 1; + cursor: text; +} + +span.path i { + display: inline-block; + font-style: normal; +} + +span.path i::before { + content: "\0020/\0020"; + color: #ccc; +} + progress { accent-color: var(--primary-color); } @@ -1534,9 +1666,11 @@ hr.cute, } #artwork-column .cover-artwork { + --normal-shadow: 0 0 12px 12px #00000080; + box-shadow: 0 2px 14px -6px var(--primary-color), - 0 0 12px 12px #00000080; + var(--normal-shadow); } #artwork-column .cover-artwork:not(:first-child), @@ -1545,6 +1679,17 @@ hr.cute, margin-right: 5px; } +#artwork-column .cover-artwork:not(:first-child) { + --normal-shadow: 0 0 9px 9px #00000068; +} + +#artwork-column .cover-artwork:first-child + .cover-artwork-joiner, +#artwork-column .cover-artwork.attached-artwork-is-main-artwork, +#artwork-column .cover-artwork.attached-artwork-is-main-artwork + .cover-artwork-joiner { + margin-left: 17.5px; + margin-right: 17.5px; +} + .cover-artwork:where(#artwork-column .cover-artwork:not(:first-child)) { margin-top: 20px; } @@ -1631,6 +1776,16 @@ p.image-details.origin-details { margin-bottom: 2px; } +p.image-details.origin-details .origin-details-line { + display: block; + margin-top: 0.25em; +} + +p.image-details.origin-details .filename-line { + display: block; + margin-top: 0.25em; +} + .cover-artwork-joiner { z-index: -2; } @@ -1702,6 +1857,16 @@ p.image-details.origin-details { color: var(--primary-color); } +.inherited-commentary-section { + clear: right; + margin-top: 1em; + margin-bottom: 1.5em; + margin-right: min(4vw, 60px); + border: 2px solid var(--deep-color); + border-radius: 4px; + background: #ffffff07; +} + .commentary-art { float: right; width: 30%; @@ -1725,8 +1890,47 @@ p.image-details.origin-details { font-weight: 800; } -.lyrics-entry { - padding-left: 40px; +.lyrics-entry .lyrics-details, +.lyrics-entry .origin-details { + font-size: 0.9em; + font-style: oblique; +} + +.lyrics-entry .lyrics-details { + margin-bottom: 0; +} + +.lyrics-entry .origin-details { + margin-top: 0.25em; +} + +.lyrics-entry.long-lyrics { + clip-path: inset(-15px -20px); +} + +.lyrics-entry.long-lyrics::after { + content: ""; + pointer-events: none; + display: block; + + /* Slight stretching past the bottom of the screen seems + * to make resizing the window (and "revealing" that area) + * a bit smoother. + */ + position: fixed; + bottom: -20px; + left: 0; + right: 0; + + height: calc(20px + min(90px, 13.5vh)); + background: linear-gradient(to bottom, transparent, black 70%, black); + opacity: 0.6; +} + +.lyrics-entry sup { + vertical-align: text-top; + opacity: 0.8; + cursor: default; } .js-hide, @@ -1736,25 +1940,32 @@ p.image-details.origin-details { } .content-image-container, -.content-video-container { +.content-video-container, +.content-audio-container { margin-top: 1em; margin-bottom: 1em; } -.content-image-container.align-center, -.content-video-container.align-center, -.content-audio-container.align-center { +.content-image-container.align-center { text-align: center; margin-top: 1.5em; margin-bottom: 1.5em; } -a.align-center, img.align-center, audio.align-center { +.content-image-container.align-full { + width: 100%; +} + +a.align-center, img.align-center, audio.align-center, video.align-center { display: block; margin-left: auto; margin-right: auto; } +a.align-full, a.align-full img, img.align-full, video.align-full { + width: 100%; +} + center { margin-top: 1em; margin-bottom: 1em; @@ -1805,6 +2016,11 @@ h1 { white-space: nowrap; } +#content details { + margin-top: 0.25em; + margin-bottom: 0.25em; +} + #content.top-index h1, #content.flash-index h1 { text-align: center; @@ -1869,6 +2085,36 @@ ul.quick-info li:not(:last-child)::after { text-align: center; } +.gallery-view-switcher, +.gallery-style-selector { + margin-left: auto; + margin-right: auto; + text-align: center; + line-height: 1.4; +} + +.gallery-style-selector .styles { + display: inline-flex; + justify-content: center; +} + +.gallery-style-selector .styles label:not(:last-child) { + margin-right: 1.25ch; +} + +.gallery-style-selector .count { + font-size: 0.85em; + + position: relative; + bottom: -0.25em; + + opacity: 0.9; +} + +#content.top-index section { + margin-bottom: 1.5em; +} + .quick-description:not(.has-external-links-only) { --clamped-padding-ratio: max(var(--responsive-padding-ratio), 0.06); margin-left: auto; @@ -1899,6 +2145,7 @@ ul.quick-info li:not(:last-child)::after { .quick-description > blockquote { margin-left: 0 !important; + margin-right: 0 !important; } .quick-description .description-content.long hr ~ p { @@ -2037,6 +2284,13 @@ ul > li.has-details { margin-left: -17px; } +li .origin-details { + display: block; + margin-left: 2ch; + font-size: 0.9em; + font-style: oblique; +} + .album-group-list dt, .group-series-list dt { font-style: oblique; @@ -2087,31 +2341,54 @@ ul > li.has-details { #content hr { border: 1px inset #808080; - width: 100%; +} + +#content hr.split { + color: #808080; } #content hr.split::before { content: "(split)"; - color: #808080; } -#content hr.split { +#content hr.main-separator { + color: var(--dim-color); + clear: none; + margin-top: -0.25em; + margin-bottom: 1.75em; +} + +#content hr.main-separator::before { + content: "♦"; + font-size: 1.2em; +} + +#content hr.split, +#content hr.main-separator { position: relative; overflow: hidden; border: none; } -#content hr.split::after { +#content hr.split::after, +#content hr.main-separator::after { display: inline-block; content: ""; - border: 1px inset #808080; - width: 100%; + width: calc(100% - min(calc(8vw - 35px), 45px)); position: absolute; top: 50%; - margin-top: -2px; margin-left: 10px; } +#content hr.split::after { + border: 1px inset currentColor; + margin-top: -2px; +} + +#content hr.main-separator::after { + border-bottom: 1px solid currentColor; +} + li > ul { margin-top: 5px; } @@ -2191,6 +2468,65 @@ html[data-url-key="localized.albumCommentary"] p.track-info { margin-left: 20px; } +html[data-url-key="localized.artistRollingWindow"] #content p { + text-align: center; +} + +html[data-url-key="localized.artistRollingWindow"] #content input[type=number] { + width: 3em; + margin: 0 0.25em; + background: black; + color: white; + border: 1px dotted var(--primary-color); + padding: 4px; + border-radius: 3px; +} + +html[data-url-key="localized.artistRollingWindow"] #timeframe-selection-control a { + display: inline-block; + padding: 5px; + text-decoration: underline; + text-decoration-style: dotted; +} + +html[data-url-key="localized.artistRollingWindow"] #timeframe-selection-control a:not([href]) { + text-decoration: none; + opacity: 0.7; +} + +html[data-url-key="localized.artistRollingWindow"] #timeframe-source-area { + border: 1px dashed #ffffff42; + border-top-style: solid; + border-bottom-style: solid; + + display: flex; + flex-direction: column; + justify-content: center; + min-height: calc(100vh - 260px); +} + +html[data-url-key="localized.artistRollingWindow"] #timeframe-source-area .grid-listing { + width: 100%; +} + +html[data-url-key="localized.artistRollingWindow"] .grid-item.peeking { + opacity: 0.8; + background: #ffffff24; +} + +html[data-url-key="localized.artistRollingWindow"] .grid-item > span:not(:first-of-type) { + display: flex; + flex-direction: row; + justify-content: center; + flex-wrap: wrap; +} + +html[data-url-key="localized.artistRollingWindow"] .grid-item > span:not(:first-of-type) > *:not([style*="display: none"]) ~ *::before { + content: '\00b7'; + margin-left: 0.5ch; + margin-right: 0.5ch; +} + html[data-url-key="localized.groupInfo"] .by a { color: var(--page-primary-color); } @@ -2229,7 +2565,9 @@ h1 a[href="#additional-names-box"]:hover { max-width: min(60vw, 600px); padding: 15px 20px 10px 20px; +} +#additional-names-box:not(.always-visible) { display: none; } @@ -2425,7 +2763,33 @@ html[data-url-key="localized.listing"][data-url-value0="tags/network"] dl dt:las linear-gradient(#000000bb, #000000bb), var(--primary-color); - box-shadow: 0 -2px 6px -1px var(--dim-color) inset; + --drop-shadow: 0 -2px 6px -1px var(--dim-color) inset; + box-shadow: var(--drop-shadow); +} + +.drop.shiny { + cursor: default; +} + +@supports (box-shadow: 1px 1px 1px color-mix(in srgb, blue, 40% red)) { + @property --drop-shine { + syntax: '<percentage>'; + initial-value: 0%; + inherits: false; + } + + .drop.shiny { + cursor: default; + transition: --drop-shine 0.2s; + } + + .drop.shiny:hover { + --drop-shine: 100%; + + box-shadow: + var(--drop-shadow), + 0 2px 4px -0.5px color-mix(in srgb, var(--primary-color), calc(100% - var(--drop-shine)) transparent); + } } .commentary-drop { @@ -2457,7 +2821,8 @@ html[data-url-key="localized.listing"][data-url-value0="tags/network"] dl dt:las /* Videos and audios (in content) get a lite version of image-container. */ .content-video-container, .content-audio-container { - width: min-content; + width: fit-content; + max-width: 100%; background-color: var(--dark-color); border: 2px solid var(--primary-color); border-radius: 2.5px 2.5px 3px 3px; @@ -2467,6 +2832,30 @@ html[data-url-key="localized.listing"][data-url-value0="tags/network"] dl dt:las .content-video-container video, .content-audio-container audio { display: block; + max-width: 100%; +} + +.content-video-container.align-center, +.content-audio-container.align-center { + margin-left: auto; + margin-right: auto; +} + +.content-video-container.align-full, +.content-audio-container.align-full { + width: 100%; +} + +.content-audio-container .filename { + color: white; + font-family: monospace; + display: block; + font-size: 0.9em; + padding-left: 1ch; + padding-right: 1ch; + padding-bottom: 0.25em; + margin-bottom: 0.5em; + border-bottom: 1px solid #fff4; } .image-text-area { @@ -2523,6 +2912,23 @@ img { object-fit: cover; } +.image { + --reveal-filter: ; + --shadow-filter: ; + + backdrop-filter: blur(0); + filter: + var(--reveal-filter) + var(--shadow-filter); +} + +p > img, li > img { + max-width: 100%; + object-fit: contain; + height: auto; + vertical-align: text-bottom; +} + .image-inner-area::after { content: ""; display: block; @@ -2582,9 +2988,9 @@ video.pixelate, .pixelate video { text-decoration-style: dotted; } -.reveal .image { +.reveal:not(.revealed) .image { opacity: 0.7; - filter: blur(20px) brightness(0.7); + --reveal-filter: blur(20px) brightness(0.7); } .reveal .image.reveal-thumbnail { @@ -2608,7 +3014,6 @@ video.pixelate, .pixelate video { } .reveal.revealed .image { - filter: none; opacity: 1; } @@ -2619,7 +3024,6 @@ video.pixelate, .pixelate video { .reveal:not(.revealed) .image-outer-area > * { --reveal-border-radius: 6px; position: relative; - overflow: hidden; border-radius: var(--reveal-border-radius); } @@ -2655,7 +3059,7 @@ video.pixelate, .pixelate video { } .reveal:not(.revealed) .image-outer-area > *:hover .image { - filter: blur(20px) brightness(0.6); + --reveal-filter: blur(20px) brightness(0.6); opacity: 0.6; } @@ -2681,20 +3085,88 @@ video.pixelate, .pixelate video { justify-content: center; align-items: flex-start; padding: 5px 15px; + box-sizing: border-box; +} + +.grid-listing:not(:has(.grid-item:not([class*="hidden-by-"]))) { + padding-bottom: 140px; + background: #cccccc07; + border-radius: 10px; + border: 1px dashed #fff3; +} + +.grid-listing .reveal-all-container { + flex-basis: 100%; +} + +.grid-listing:not(:has(.grid-item:not([class*="hidden-by-"]))) .reveal-all-container { + display: none; +} + +.grid-listing .reveal-all-container.has-nearby-tab { + margin-bottom: 0.6em; +} + +.grid-listing .reveal-all { + max-width: 400px; + margin: 0.20em auto 0; + text-align: center; +} + +.grid-listing .reveal-all .warnings:not(.reveal-all:hover *) { + opacity: 0.4; +} + +.grid-listing .reveal-all a { + display: inline-block; + margin-bottom: 0.15em; + + text-decoration: underline; + text-decoration-style: dotted; +} + +.grid-listing .reveal-all b { + white-space: nowrap; } .grid-item { + line-height: 1.2; font-size: 0.9em; } .grid-item { + --tab-pull: 0px; + --tabnt-offset: 0px; + display: inline-block; text-align: center; background-color: #111111; border: 1px dotted var(--primary-color); border-radius: 2px; padding: 5px; + margin: 10px; + margin-top: + calc( + 10px + - var(--tab-pull) + + var(--tabnt-offset)); +} + +.grid-item.has-tab { + border-radius: 8px 8px 3px 3px; +} + +.grid-item.has-tab:hover { + --tab-pull: 3px; +} + +.grid-item:not(.has-tab) { + --tabnt-offset: calc(1.2em - 4px); +} + +.grid-item[class*="hidden-by-"] { + display: none; } .grid-item .image-container { @@ -2711,10 +3183,16 @@ video.pixelate, .pixelate video { } .grid-item .image { + --shadow-filter: + drop-shadow(0 3px 2px #0004) + drop-shadow(0 1px 5px #0001) + drop-shadow(0 3px 4px #0001); + width: 100%; height: 100% !important; margin-top: auto; margin-bottom: auto; + object-fit: contain; } .grid-item:hover { @@ -2731,20 +3209,27 @@ video.pixelate, .pixelate video { hyphens: auto; } -.grid-item > span:not(:first-child) { - margin-top: 2px; -} +/* tab */ +.grid-item > span:first-child { + margin-bottom: calc(3px + var(--tab-pull)); -.grid-item > span:first-of-type { - margin-top: 6px; + font-style: oblique; } -.grid-item > span:not(:first-of-type) { +/* info */ +.grid-item > .image-container + span ~ span { + margin-top: 2px; + font-size: 0.9em; opacity: 0.8; } -.grid-item:hover > span:first-of-type { +/* title */ +.grid-item > .image-container + span { + margin-top: 6px; +} + +.grid-item:hover > .image-container + span { text-decoration: underline; } @@ -2753,6 +3238,10 @@ video.pixelate, .pixelate video { max-width: 200px; } +.grid-name-marker { + color: white; +} + .grid-actions { display: flex; flex-direction: row; @@ -2770,6 +3259,47 @@ video.pixelate, .pixelate video { --dim-color: inherit !important; } +.grid-caption { + flex-basis: 100%; + text-align: center; + line-height: 1.5; +} + +.grid-expando { + margin-top: 1em; + margin-bottom: 2em; + flex-basis: 100%; + + display: flex; + flex-direction: row; + justify-content: space-around; +} + +.grid-expando-content { + margin: 0; + text-align: center; + line-height: 1.5; +} + +.grid-expando-toggle { + text-decoration: underline; + text-decoration-style: dotted; +} + +.grid-item.shown-by-expandable-cut { + animation: expand-cover-grid 0.8s forwards; +} + +@keyframes expand-cover-grid { + from { + opacity: 0; + } + + to { + opacity: 1; + } +} + /* Carousel */ .carousel-container { @@ -2790,7 +3320,6 @@ video.pixelate, .pixelate video { left: 0; right: 0; bottom: 0; - z-index: -20; background-color: var(--dim-color); filter: brightness(0.6); } @@ -3075,6 +3604,48 @@ h3.content-heading { clear: both; } +summary.content-heading { + list-style-type: none; +} + +summary.content-heading .cue { + display: inline-flex; + color: var(--primary-color); +} + +summary.content-heading .cue::after { + content: ""; + padding-left: 0.5ch; + display: list-item; + list-style-type: disclosure-closed; + list-style-position: inside; +} + +details[open] > summary.content-heading .cue::after { + list-style-type: disclosure-open; +} + +summary.content-heading > span:hover { + text-decoration: none !important; +} + +summary.content-heading > span:hover .cue { + text-decoration: underline; + text-decoration-style: wavy; +} + +summary.content-heading .when-open { + display: none; +} + +details[open] > summary.content-heading .when-open { + display: unset; +} + +details[open] > summary.content-heading .when-collapsed { + display: none; +} + /* This animation's name is referenced in JavaScript */ @keyframes highlight-hash-link { 0% { @@ -3180,15 +3751,11 @@ main.long-content .content-sticky-heading-container .content-sticky-subheading-r grid-template-columns: 1fr min(40%, 90px); } -.content-sticky-heading-root.has-cover { - padding-right: min(40%, 400px); -} - .content-sticky-heading-row h1 { position: relative; margin: 0; padding-right: 20px; - line-height: 1.4; + overflow-x: hidden; } .content-sticky-heading-row h1 .reference-collapsed-heading { @@ -3328,7 +3895,9 @@ main.long-content .content-sticky-heading-container .content-sticky-subheading-r } #content, .sidebar { - contain: paint; + /* In the year of our pizza 2025, we try commenting this out. + */ + /*contain: paint;*/ } /* Sticky sidebar */ @@ -3715,6 +4284,16 @@ main.long-content .content-sticky-heading-container .content-sticky-subheading-r max-width: unset; } + #artwork-column .cover-artwork { + --normal-shadow: 0 0 transparent; + } + + #artwork-column .cover-artwork:not(:first-child), + #artwork-column .cover-artwork-joiner { + margin-left: 30px; + margin-right: 30px; + } + #additional-names-box { width: unset; max-width: unset; diff --git a/src/static/js/client-util.js b/src/static/js/client-util.js index 71112313..764c1480 100644 --- a/src/static/js/client-util.js +++ b/src/static/js/client-util.js @@ -1,5 +1,3 @@ -/* eslint-env browser */ - export function rebase(href, rebaseKey = 'rebaseLocalized') { let result = document.documentElement.dataset[rebaseKey] || './'; @@ -37,7 +35,7 @@ export function cssProp(el, ...args) { } } -export function templateContent(el) { +export function templateContent(el, slots = {}) { if (el === null) { return null; } @@ -46,7 +44,25 @@ export function templateContent(el) { throw new Error(`Expected a <template> element`); } - return el.content.cloneNode(true); + const content = el.content.cloneNode(true); + + for (const [key, value] of Object.entries(slots)) { + const slot = content.querySelector(`slot[name="${key}"]`); + + if (!slot) { + console.warn(`Slot ${key} missing in template:`, el); + continue; + } + + if (value === null || value === undefined) { + console.warn(`Valueless slot ${key} in template:`, el); + continue; + } + + slot.replaceWith(value); + } + + return content; } // Curry-style, so multiple points can more conveniently be tested at once. @@ -127,3 +143,10 @@ export function dispatchInternalEvent(event, eventName, ...args) { return results; } + +const languageCode = document.documentElement.getAttribute('lang'); + +export function formatDate(inputDate) { + const date = new Date(inputDate); + return date.toLocaleDateString(languageCode); +} diff --git a/src/static/js/client/additional-names-box.js b/src/static/js/client/additional-names-box.js index 195ba25d..a6d9b098 100644 --- a/src/static/js/client/additional-names-box.js +++ b/src/static/js/client/additional-names-box.js @@ -1,5 +1,3 @@ -/* eslint-env browser */ - import {cssProp} from '../client-util.js'; import {info as hashLinkInfo} from './hash-link.js'; diff --git a/src/static/js/client/album-commentary-sidebar.js b/src/static/js/client/album-commentary-sidebar.js index c5eaf81b..d7c4a591 100644 --- a/src/static/js/client/album-commentary-sidebar.js +++ b/src/static/js/client/album-commentary-sidebar.js @@ -1,5 +1,3 @@ -/* eslint-env browser */ - import {empty} from '../../shared-util/sugar.js'; import {info as hashLinkInfo} from './hash-link.js'; diff --git a/src/static/js/client/art-tag-gallery-filter.js b/src/static/js/client/art-tag-gallery-filter.js index fd40d1a2..f74f640e 100644 --- a/src/static/js/client/art-tag-gallery-filter.js +++ b/src/static/js/client/art-tag-gallery-filter.js @@ -1,5 +1,3 @@ -/* eslint-env browser */ - export const info = { id: 'artTagGalleryFilterInfo', diff --git a/src/static/js/client/art-tag-network.js b/src/static/js/client/art-tag-network.js index 44e10c11..d0576152 100644 --- a/src/static/js/client/art-tag-network.js +++ b/src/static/js/client/art-tag-network.js @@ -1,5 +1,3 @@ -/* eslint-env browser */ - import {cssProp} from '../client-util.js'; import {atOffset, stitchArrays} from '../../shared-util/sugar.js'; diff --git a/src/static/js/client/artist-external-link-tooltip.js b/src/static/js/client/artist-external-link-tooltip.js index 21ddfb91..2eadf916 100644 --- a/src/static/js/client/artist-external-link-tooltip.js +++ b/src/static/js/client/artist-external-link-tooltip.js @@ -1,5 +1,3 @@ -/* eslint-env browser */ - import {accumulateSum, empty} from '../../shared-util/sugar.js'; import {info as hoverableTooltipInfo, repositionCurrentTooltip} diff --git a/src/static/js/client/artist-rolling-window.js b/src/static/js/client/artist-rolling-window.js new file mode 100644 index 00000000..b8ff7354 --- /dev/null +++ b/src/static/js/client/artist-rolling-window.js @@ -0,0 +1,571 @@ +import {cssProp, formatDate} from '../client-util.js'; + +import {sortByDate} from '../../shared-util/sort.js'; +import {chunkByConditions, chunkByProperties, empty, stitchArrays} + from '../../shared-util/sugar.js'; + +export const info = { + id: 'artistRollingWindowInfo', + + timeframeMonthsBefore: null, + timeframeMonthsAfter: null, + timeframeMonthsPeek: null, + + contributionKind: null, + contributionGroup: null, + + timeframeSelectionSomeLine: null, + timeframeSelectionNoneLine: null, + + timeframeSelectionContributionCount: null, + timeframeSelectionTimeframeCount: null, + timeframeSelectionFirstDate: null, + timeframeSelectionLastDate: null, + + timeframeSelectionControl: null, + timeframeSelectionMenu: null, + timeframeSelectionPrevious: null, + timeframeSelectionNext: null, + + timeframeEmptyLine: null, + + sourceArea: null, + sourceGrid: null, + sources: null, +}; + +export function getPageReferences() { + if (document.documentElement.dataset.urlKey !== 'localized.artistRollingWindow') { + return; + } + + info.timeframeMonthsBefore = + document.getElementById('timeframe-months-before'); + + info.timeframeMonthsAfter = + document.getElementById('timeframe-months-after'); + + info.timeframeMonthsPeek = + document.getElementById('timeframe-months-peek'); + + info.contributionKind = + document.getElementById('contribution-kind'); + + info.contributionGroup = + document.getElementById('contribution-group'); + + info.timeframeSelectionSomeLine = + document.getElementById('timeframe-selection-some'); + + info.timeframeSelectionNoneLine = + document.getElementById('timeframe-selection-none'); + + info.timeframeSelectionContributionCount = + document.getElementById('timeframe-selection-contribution-count'); + + info.timeframeSelectionTimeframeCount = + document.getElementById('timeframe-selection-timeframe-count'); + + info.timeframeSelectionFirstDate = + document.getElementById('timeframe-selection-first-date'); + + info.timeframeSelectionLastDate = + document.getElementById('timeframe-selection-last-date'); + + info.timeframeSelectionControl = + document.getElementById('timeframe-selection-control'); + + info.timeframeSelectionMenu = + document.getElementById('timeframe-selection-menu'); + + info.timeframeSelectionPrevious = + document.getElementById('timeframe-selection-previous'); + + info.timeframeSelectionNext = + document.getElementById('timeframe-selection-next'); + + info.timeframeEmptyLine = + document.getElementById('timeframe-empty'); + + info.sourceArea = + document.getElementById('timeframe-source-area'); + + info.sourceGrid = + info.sourceArea.querySelector('.grid-listing'); + + info.sources = + info.sourceGrid.getElementsByClassName('grid-item'); +} + +export function addPageListeners() { + if (!info.sourceArea) { + return; + } + + for (const input of [ + info.timeframeMonthsBefore, + info.timeframeMonthsAfter, + info.timeframeMonthsPeek, + info.contributionKind, + info.contributionGroup, + ]) { + input.addEventListener('change', () => { + updateArtistRollingWindow() + }); + } + + info.timeframeSelectionMenu.addEventListener('change', () => { + updateRollingWindowTimeframeSelection(); + }); + + const eatClicks = (element, callback) => { + element.addEventListener('click', domEvent => { + domEvent.preventDefault(); + callback(); + }); + + element.addEventListener('mousedown', domEvent => { + if (domEvent.detail > 1) { + domEvent.preventDefault(); + } + }); + }; + + eatClicks(info.timeframeSelectionNext, nextRollingTimeframeSelection); + eatClicks(info.timeframeSelectionPrevious, previousRollingTimeframeSelection); +} + +export function mutatePageContent() { + if (!info.sourceArea) { + return; + } + + updateArtistRollingWindow(); +} + +function previousRollingTimeframeSelection() { + const menu = info.timeframeSelectionMenu; + + if (menu.selectedIndex > 0) { + menu.selectedIndex--; + } + + updateRollingWindowTimeframeSelection(); +} + +function nextRollingTimeframeSelection() { + const menu = info.timeframeSelectionMenu; + + if (menu.selectedIndex < menu.length - 1) { + menu.selectedIndex++; + } + + updateRollingWindowTimeframeSelection(); +} + +function getArtistRollingWindowSourceInfo() { + const sourceElements = + Array.from(info.sources); + + const sourceTimeElements = + sourceElements + .map(el => Array.from(el.getElementsByTagName('time'))); + + const sourceTimeClasses = + sourceTimeElements + .map(times => times + .map(time => Array.from(time.classList))); + + const sourceKinds = + sourceTimeClasses + .map(times => times + .map(classes => classes + .find(cl => cl.endsWith('-contribution-date')) + .slice(0, -'-contribution-date'.length))); + + const sourceGroups = + sourceElements + .map(el => + Array.from(el.querySelectorAll('.contribution-group')) + .map(data => data.value)); + + const sourceDates = + sourceTimeElements + .map(times => times + .map(time => new Date(time.getAttribute('datetime')))); + + return stitchArrays({ + element: sourceElements, + kinds: sourceKinds, + groups: sourceGroups, + dates: sourceDates, + }); +} + +function getArtistRollingWindowTimeframeInfo() { + const contributionKind = + info.contributionKind.value; + + const contributionGroup = + info.contributionGroup.value; + + const sourceInfo = + getArtistRollingWindowSourceInfo(); + + const principalSources = + sourceInfo.filter(source => { + if (!source.kinds.includes(contributionKind)) { + return false; + } + + if (contributionGroup !== '-') { + if (!source.groups.includes(contributionGroup)) { + return false; + } + } + + return true; + }); + + const principalSourceDates = + principalSources.map(source => + stitchArrays({ + kind: source.kinds, + date: source.dates, + }).find(({kind}) => kind === contributionKind) + .date); + + const getPeekDate = inputDate => { + const date = new Date(inputDate); + + date.setMonth( + (date.getMonth() + - parseInt(info.timeframeMonthsBefore.value) + - parseInt(info.timeframeMonthsPeek.value))); + + return date; + }; + + const getEntranceDate = inputDate => { + const date = new Date(inputDate); + + date.setMonth( + (date.getMonth() + - parseInt(info.timeframeMonthsBefore.value))); + + return date; + }; + + const getExitDate = inputDate => { + const date = new Date(inputDate); + + date.setMonth( + (date.getMonth() + + parseInt(info.timeframeMonthsAfter.value))); + + return date; + }; + + const principalSourceIndices = + Array.from({length: principalSources.length}, (_, i) => i); + + const timeframeSourceChunks = + chunkByConditions(principalSourceIndices, [ + (previous, next) => + +principalSourceDates[previous] !== + +principalSourceDates[next], + ]); + + const timeframeSourceChunkDates = + timeframeSourceChunks + .map(indices => indices[0]) + .map(index => principalSourceDates[index]); + + const timeframeSourceChunkPeekDates = + timeframeSourceChunkDates + .map(getPeekDate); + + const timeframeSourceChunkEntranceDates = + timeframeSourceChunkDates + .map(getEntranceDate); + + const timeframeSourceChunkExitDates = + timeframeSourceChunkDates + .map(getExitDate); + + const peekDateInfo = + stitchArrays({ + peek: timeframeSourceChunkPeekDates, + indices: timeframeSourceChunks, + }).map(({peek, indices}) => ({ + date: peek, + peek: indices, + })); + + const entranceDateInfo = + stitchArrays({ + entrance: timeframeSourceChunkEntranceDates, + indices: timeframeSourceChunks, + }).map(({entrance, indices}) => ({ + date: entrance, + entrance: indices, + })); + + const exitDateInfo = + stitchArrays({ + exit: timeframeSourceChunkExitDates, + indices: timeframeSourceChunks, + }).map(({exit, indices}) => ({ + date: exit, + exit: indices, + })); + + const dateInfoChunks = + chunkByProperties( + sortByDate([ + ...peekDateInfo, + ...entranceDateInfo, + ...exitDateInfo, + ]), + ['date']); + + const dateInfo = + dateInfoChunks + .map(({chunk}) => + Object.assign({ + peek: null, + entrance: null, + exit: null, + }, ...chunk)); + + const timeframeInfo = + dateInfo.reduce( + (accumulator, {date, peek, entrance, exit}) => { + const previous = accumulator.at(-1); + + // These mustn't be mutated! + let peeking = (previous ? previous.peeking : []); + let tracking = (previous ? previous.tracking : []); + + if (peek) { + peeking = + peeking.concat(peek); + } + + if (entrance) { + peeking = + peeking.filter(index => !entrance.includes(index)); + + tracking = + tracking.concat(entrance); + } + + if (exit) { + tracking = + tracking.filter(index => !exit.includes(index)); + } + + return [...accumulator, { + date, + peeking, + tracking, + peek, + entrance, + exit, + }]; + }, + []); + + const indicesToSources = indices => + (indices + ? indices.map(index => principalSources[index]) + : null); + + const finalizedTimeframeInfo = + timeframeInfo.map(({ + date, + peeking, + tracking, + peek, + entrance, + exit, + }) => ({ + date, + peeking: indicesToSources(peeking), + tracking: indicesToSources(tracking), + peek: indicesToSources(peek), + entrance: indicesToSources(entrance), + exit: indicesToSources(exit), + })); + + return finalizedTimeframeInfo; +} + +function updateArtistRollingWindow() { + const timeframeInfo = + getArtistRollingWindowTimeframeInfo(); + + if (empty(timeframeInfo)) { + cssProp(info.timeframeSelectionControl, 'display', 'none'); + cssProp(info.timeframeSelectionSomeLine, 'display', 'none'); + cssProp(info.timeframeSelectionNoneLine, 'display', null); + + updateRollingWindowTimeframeSelection(timeframeInfo); + + return; + } + + cssProp(info.timeframeSelectionControl, 'display', null); + cssProp(info.timeframeSelectionSomeLine, 'display', null); + cssProp(info.timeframeSelectionNoneLine, 'display', 'none'); + + // The last timeframe is just the exit of the final tracked sources, + // so we aren't going to display a menu option for it, and will just use + // it as the end of the final option's date range. + + const usedTimeframes = timeframeInfo.slice(0, -1); + const firstTimeframe = timeframeInfo.at(0); + const lastTimeframe = timeframeInfo.at(-1); + + const sourceCount = + timeframeInfo + .flatMap(({entrance}) => entrance ?? []) + .length; + + const timeframeCount = + usedTimeframes.length; + + info.timeframeSelectionContributionCount.innerText = sourceCount; + info.timeframeSelectionTimeframeCount.innerText = timeframeCount; + + const firstDate = firstTimeframe.date; + const lastDate = lastTimeframe.date; + + info.timeframeSelectionFirstDate.innerText = formatDate(firstDate); + info.timeframeSelectionLastDate.innerText = formatDate(lastDate); + + while (info.timeframeSelectionMenu.firstChild) { + info.timeframeSelectionMenu.firstChild.remove(); + } + + for (const [index, timeframe] of usedTimeframes.entries()) { + const nextTimeframe = timeframeInfo[index + 1]; + + const option = document.createElement('option'); + + option.appendChild(document.createTextNode( + `${formatDate(timeframe.date)} – ${formatDate(nextTimeframe.date)}`)); + + info.timeframeSelectionMenu.appendChild(option); + } + + updateRollingWindowTimeframeSelection(timeframeInfo); +} + +function updateRollingWindowTimeframeSelection(timeframeInfo) { + timeframeInfo ??= getArtistRollingWindowTimeframeInfo(); + + updateRollingWindowTimeframeSelectionControls(timeframeInfo); + updateRollingWindowTimeframeSelectionSources(timeframeInfo); +} + +function updateRollingWindowTimeframeSelectionControls(timeframeInfo) { + const currentIndex = + info.timeframeSelectionMenu.selectedIndex; + + const atFirstTimeframe = + currentIndex === 0; + + // The last actual timeframe is empty and not displayed as a menu option. + const atLastTimeframe = + currentIndex === timeframeInfo.length - 2; + + if (atFirstTimeframe) { + info.timeframeSelectionPrevious.removeAttribute('href'); + } else { + info.timeframeSelectionPrevious.setAttribute('href', '#'); + } + + if (atLastTimeframe) { + info.timeframeSelectionNext.removeAttribute('href'); + } else { + info.timeframeSelectionNext.setAttribute('href', '#'); + } +} + +function updateRollingWindowTimeframeSelectionSources(timeframeInfo) { + const currentIndex = + info.timeframeSelectionMenu.selectedIndex; + + const contributionGroup = + info.contributionGroup.value; + + cssProp(info.sourceGrid, 'display', null); + + const {peeking: peekingSources, tracking: trackingSources} = + (empty(timeframeInfo) + ? {peeking: [], tracking: []} + : timeframeInfo[currentIndex]); + + const peekingElements = + peekingSources.map(source => source.element); + + const trackingElements = + trackingSources.map(source => source.element); + + const showingElements = + [...trackingElements, ...peekingElements]; + + const hidingElements = + Array.from(info.sources) + .filter(element => + !peekingElements.includes(element) && + !trackingElements.includes(element)); + + for (const element of peekingElements) { + element.classList.add('peeking'); + element.classList.remove('tracking'); + } + + for (const element of trackingElements) { + element.classList.remove('peeking'); + element.classList.add('tracking'); + } + + for (const element of hidingElements) { + element.classList.remove('peeking'); + element.classList.remove('tracking'); + cssProp(element, 'display', 'none'); + } + + for (const element of showingElements) { + cssProp(element, 'display', null); + + for (const time of element.getElementsByTagName('time')) { + for (const className of time.classList) { + if (!className.endsWith('-contribution-date')) continue; + + const kind = className.slice(0, -'-contribution-date'.length); + if (kind === info.contributionKind.value) { + cssProp(time, 'display', null); + } else { + cssProp(time, 'display', 'none'); + } + } + } + + for (const data of element.getElementsByClassName('contribution-group')) { + if (contributionGroup === '-' || data.value !== contributionGroup) { + cssProp(data, 'display', null); + } else { + cssProp(data, 'display', 'none'); + } + } + } + + if (empty(peekingElements) && empty(trackingElements)) { + cssProp(info.timeframeEmptyLine, 'display', null); + } else { + cssProp(info.timeframeEmptyLine, 'display', 'none'); + } +} diff --git a/src/static/js/client/css-compatibility-assistant.js b/src/static/js/client/css-compatibility-assistant.js index aa637cc4..37b0645a 100644 --- a/src/static/js/client/css-compatibility-assistant.js +++ b/src/static/js/client/css-compatibility-assistant.js @@ -1,5 +1,3 @@ -/* eslint-env browser */ - import {stitchArrays} from '../../shared-util/sugar.js'; export const info = { diff --git a/src/static/js/client/datetimestamp-tooltip.js b/src/static/js/client/datetimestamp-tooltip.js index 46d1cd5b..00530484 100644 --- a/src/static/js/client/datetimestamp-tooltip.js +++ b/src/static/js/client/datetimestamp-tooltip.js @@ -1,5 +1,3 @@ -/* eslint-env browser */ - // TODO: Maybe datetimestamps can just be incorporated into text-with-tooltip? import {stitchArrays} from '../../shared-util/sugar.js'; diff --git a/src/static/js/client/dragged-link.js b/src/static/js/client/dragged-link.js index 56021e7f..3a4ee314 100644 --- a/src/static/js/client/dragged-link.js +++ b/src/static/js/client/dragged-link.js @@ -1,5 +1,3 @@ -/* eslint-env browser */ - export const info = { id: `draggedLinkInfo`, diff --git a/src/static/js/client/expandable-grid-section.js b/src/static/js/client/expandable-grid-section.js new file mode 100644 index 00000000..4d6e0058 --- /dev/null +++ b/src/static/js/client/expandable-grid-section.js @@ -0,0 +1,83 @@ +import {cssProp} from '../client-util.js'; + +import {stitchArrays} from '../../shared-util/sugar.js'; + +export const info = { + id: 'expandableGallerySectionInfo', + + items: null, + toggles: null, + expandCues: null, + collapseCues: null, +}; + +export function getPageReferences() { + const expandos = + Array.from(document.querySelectorAll('.grid-expando')); + + const grids = + expandos + .map(expando => expando.closest('.grid-listing')); + + info.items = + grids + .map(grid => grid.querySelectorAll('.grid-item')) + .map(items => Array.from(items)); + + info.toggles = + expandos + .map(expando => expando.querySelector('.grid-expando-toggle')); + + info.expandCues = + info.toggles + .map(toggle => toggle.querySelector('.grid-expand-cue')); + + info.collapseCues = + info.toggles + .map(toggle => toggle.querySelector('.grid-collapse-cue')); +} + +export function addPageListeners() { + stitchArrays({ + items: info.items, + toggle: info.toggles, + expandCue: info.expandCues, + collapseCue: info.collapseCues, + }).forEach(({ + items, + toggle, + expandCue, + collapseCue, + }) => { + toggle.addEventListener('click', domEvent => { + domEvent.preventDefault(); + + const collapsed = + items.some(item => + item.classList.contains('hidden-by-expandable-cut')); + + for (const item of items) { + if ( + !item.classList.contains('hidden-by-expandable-cut') && + !item.classList.contains('shown-by-expandable-cut') + ) continue; + + if (collapsed) { + item.classList.remove('hidden-by-expandable-cut'); + item.classList.add('shown-by-expandable-cut'); + } else { + item.classList.add('hidden-by-expandable-cut'); + item.classList.remove('shown-by-expandable-cut'); + } + } + + if (collapsed) { + cssProp(expandCue, 'display', 'none'); + cssProp(collapseCue, 'display', null); + } else { + cssProp(expandCue, 'display', null); + cssProp(collapseCue, 'display', 'none'); + } + }); + }); +} diff --git a/src/static/js/client/gallery-style-selector.js b/src/static/js/client/gallery-style-selector.js new file mode 100644 index 00000000..44f98ac3 --- /dev/null +++ b/src/static/js/client/gallery-style-selector.js @@ -0,0 +1,121 @@ +import {cssProp} from '../client-util.js'; + +import {stitchArrays} from '../../shared-util/sugar.js'; + +export const info = { + id: 'galleryStyleSelectorInfo', + + selectors: null, + sections: null, + + selectorStyleInputs: null, + selectorStyleInputStyles: null, + + selectorReleaseItems: null, + selectorReleaseItemStyles: null, + + selectorCountAll: null, + selectorCountFiltered: null, + selectorCountFilteredCount: null, + selectorCountNone: null, +}; + +export function getPageReferences() { + info.selectors = + Array.from(document.querySelectorAll('.gallery-style-selector')); + + info.sections = + info.selectors + .map(selector => selector.closest('section')); + + info.selectorStyleInputs = + info.selectors + .map(selector => selector.querySelectorAll('.styles input')) + .map(inputs => Array.from(inputs)); + + info.selectorStyleInputStyles = + info.selectorStyleInputs + .map(inputs => inputs + .map(input => input.closest('label').dataset.style)); + + info.selectorReleaseItems = + info.sections + .map(section => section.querySelectorAll('.grid-item')) + .map(items => Array.from(items)); + + info.selectorReleaseItemStyles = + info.selectorReleaseItems + .map(items => items + .map(item => item.dataset.style)); + + info.selectorCountAll = + info.selectors + .map(selector => selector.querySelector('.count.all')); + + info.selectorCountFiltered = + info.selectors + .map(selector => selector.querySelector('.count.filtered')); + + info.selectorCountFilteredCount = + info.selectorCountFiltered + .map(selector => selector.querySelector('span')); + + info.selectorCountNone = + info.selectors + .map(selector => selector.querySelector('.count.none')); +} + +export function addPageListeners() { + for (const index of info.selectors.keys()) { + for (const input of info.selectorStyleInputs[index]) { + input.addEventListener('input', () => updateVisibleReleases(index)); + } + } +} + +function updateVisibleReleases(index) { + const inputs = info.selectorStyleInputs[index]; + const inputStyles = info.selectorStyleInputStyles[index]; + + const selectedStyles = + stitchArrays({input: inputs, style: inputStyles}) + .filter(({input}) => input.checked) + .map(({style}) => style); + + const releases = info.selectorReleaseItems[index]; + const releaseStyles = info.selectorReleaseItemStyles[index]; + + let visible = 0; + + stitchArrays({ + release: releases, + style: releaseStyles, + }).forEach(({release, style}) => { + if (selectedStyles.includes(style)) { + release.classList.remove('hidden-by-style-mismatch'); + visible++; + } else { + release.classList.add('hidden-by-style-mismatch'); + } + }); + + const countAll = info.selectorCountAll[index]; + const countFiltered = info.selectorCountFiltered[index]; + const countFilteredCount = info.selectorCountFilteredCount[index]; + const countNone = info.selectorCountNone[index]; + + if (visible === releases.length) { + cssProp(countAll, 'display', null); + cssProp(countFiltered, 'display', 'none'); + cssProp(countNone, 'display', 'none'); + } else if (visible === 0) { + cssProp(countAll, 'display', 'none'); + cssProp(countFiltered, 'display', 'none'); + cssProp(countNone, 'display', null); + } else { + cssProp(countAll, 'display', 'none'); + cssProp(countFiltered, 'display', null); + cssProp(countNone, 'display', 'none'); + countFilteredCount.innerHTML = visible; + } +} diff --git a/src/static/js/client/hash-link.js b/src/static/js/client/hash-link.js index 27035e29..02ffdc23 100644 --- a/src/static/js/client/hash-link.js +++ b/src/static/js/client/hash-link.js @@ -1,6 +1,5 @@ -/* eslint-env browser */ - -import {filterMultipleArrays, stitchArrays} from '../../shared-util/sugar.js'; +import {filterMultipleArrays, stitchArrays, unique} + from '../../shared-util/sugar.js'; import {dispatchInternalEvent} from '../client-util.js'; @@ -11,6 +10,9 @@ export const info = { hrefs: null, targets: null, + details: null, + detailsIDs: null, + state: { highlightedTarget: null, scrollingAfterClick: false, @@ -40,6 +42,19 @@ export function getPageReferences() { info.hrefs, info.targets, (_link, _href, target) => target); + + info.details = + unique([ + ...document.querySelectorAll('details[id]'), + ... + Array.from(document.querySelectorAll('summary[id]')) + .map(summary => summary.closest('details')), + ]); + + info.detailsIDs = + info.details.map(details => + details.id || + details.querySelector('summary').id); } function processScrollingAfterHashLinkClicked() { @@ -60,6 +75,15 @@ function processScrollingAfterHashLinkClicked() { }, 200); } +export function mutatePageContent() { + if (location.hash.length > 1) { + const target = document.getElementById(location.hash.slice(1)); + if (target) { + expandDetails(target); + } + } +} + export function addPageListeners() { // Instead of defining a scroll offset (to account for the sticky heading) // in JavaScript, we interface with the CSS property 'scroll-margin-top'. @@ -94,6 +118,8 @@ export function addPageListeners() { return; } + expandDetails(target); + // Hide skipper box right away, so the layout is updated on time for the // math operations coming up next. const skipper = document.getElementById('skippers'); @@ -143,4 +169,32 @@ export function addPageListeners() { state.highlightedTarget = null; }); } + + stitchArrays({ + details: info.details, + id: info.detailsIDs, + }).forEach(({details, id}) => { + details.addEventListener('toggle', () => { + if (!details.open) { + detractHash(id); + } + }); + }); +} + +function expandDetails(target) { + if (target.nodeName === 'SUMMARY') { + const details = target.closest('details'); + if (details) { + details.open = true; + } + } else if (target.nodeName === 'DETAILS') { + target.open = true; + } +} + +function detractHash(id) { + if (location.hash === '#' + id) { + history.pushState({}, undefined, location.href.replace(/#.*$/, '')); + } } diff --git a/src/static/js/client/hoverable-tooltip.js b/src/static/js/client/hoverable-tooltip.js index 9569de3e..22b9471c 100644 --- a/src/static/js/client/hoverable-tooltip.js +++ b/src/static/js/client/hoverable-tooltip.js @@ -1,5 +1,3 @@ -/* eslint-env browser */ - import {empty, filterMultipleArrays} from '../../shared-util/sugar.js'; import {WikiRect} from '../rectangles.js'; @@ -118,17 +116,17 @@ export function registerTooltipElement(tooltip) { handleTooltipMouseLeft(tooltip); }); - tooltip.addEventListener('focusin', event => { - handleTooltipReceivedFocus(tooltip, event.relatedTarget); + tooltip.addEventListener('focusin', domEvent => { + handleTooltipReceivedFocus(tooltip, domEvent.relatedTarget); }); - tooltip.addEventListener('focusout', event => { + tooltip.addEventListener('focusout', domEvent => { // This event gets activated for tabbing *between* links inside the // tooltip, which is no good and certainly doesn't represent the focus // leaving the tooltip. - if (currentlyShownTooltipHasFocus(event.relatedTarget)) return; + if (currentlyShownTooltipHasFocus(domEvent.relatedTarget)) return; - handleTooltipLostFocus(tooltip, event.relatedTarget); + handleTooltipLostFocus(tooltip, domEvent.relatedTarget); }); } @@ -158,20 +156,20 @@ export function registerTooltipHoverableElement(hoverable, tooltip) { handleTooltipHoverableMouseLeft(hoverable); }); - hoverable.addEventListener('focusin', event => { - handleTooltipHoverableReceivedFocus(hoverable, event); + hoverable.addEventListener('focusin', domEvent => { + handleTooltipHoverableReceivedFocus(hoverable, domEvent); }); - hoverable.addEventListener('focusout', event => { - handleTooltipHoverableLostFocus(hoverable, event); + hoverable.addEventListener('focusout', domEvent => { + handleTooltipHoverableLostFocus(hoverable, domEvent); }); - hoverable.addEventListener('touchend', event => { - handleTooltipHoverableTouchEnded(hoverable, event); + hoverable.addEventListener('touchend', domEvent => { + handleTooltipHoverableTouchEnded(hoverable, domEvent); }); - hoverable.addEventListener('click', event => { - handleTooltipHoverableClicked(hoverable, event); + hoverable.addEventListener('click', domEvent => { + handleTooltipHoverableClicked(hoverable, domEvent); }); } @@ -416,7 +414,7 @@ function handleTooltipHoverableTouchEnded(hoverable, domEvent) { }, 1200); } -function handleTooltipHoverableClicked(hoverable) { +function handleTooltipHoverableClicked(hoverable, domEvent) { const {state} = info; // Don't navigate away from the page if the this hoverable was recently @@ -426,7 +424,7 @@ function handleTooltipHoverableClicked(hoverable) { state.currentlyActiveHoverable === hoverable && state.hoverableWasRecentlyTouched ) { - event.preventDefault(); + domEvent.preventDefault(); } } diff --git a/src/static/js/client/image-overlay.js b/src/static/js/client/image-overlay.js index e9e2708d..0595bff7 100644 --- a/src/static/js/client/image-overlay.js +++ b/src/static/js/client/image-overlay.js @@ -1,5 +1,3 @@ -/* eslint-env browser */ - import {getColors} from '../../shared-util/colors.js'; import {cssProp} from '../client-util.js'; @@ -149,7 +147,8 @@ function getImageLinkDetails(imageLink) { a.href, embeddedSrc: - img?.src ?? + img?.src || + img?.currentSrc || a.dataset.embedSrc, originalFileSize: diff --git a/src/static/js/client/index.js b/src/static/js/client/index.js index 81ea3415..3357837e 100644 --- a/src/static/js/client/index.js +++ b/src/static/js/client/index.js @@ -1,5 +1,3 @@ -/* eslint-env browser */ - import '../group-contributions-table.js'; import * as additionalNamesBoxModule from './additional-names-box.js'; @@ -7,15 +5,20 @@ import * as albumCommentarySidebarModule from './album-commentary-sidebar.js'; import * as artTagGalleryFilterModule from './art-tag-gallery-filter.js'; import * as artTagNetworkModule from './art-tag-network.js'; import * as artistExternalLinkTooltipModule from './artist-external-link-tooltip.js'; +import * as artistRollingWindowModule from './artist-rolling-window.js'; import * as cssCompatibilityAssistantModule from './css-compatibility-assistant.js'; import * as datetimestampTooltipModule from './datetimestamp-tooltip.js'; import * as draggedLinkModule from './dragged-link.js'; +import * as expandableGridSectionModule from './expandable-grid-section.js'; +import * as galleryStyleSelectorModule from './gallery-style-selector.js'; import * as hashLinkModule from './hash-link.js'; import * as hoverableTooltipModule from './hoverable-tooltip.js'; import * as imageOverlayModule from './image-overlay.js'; import * as intrapageDotSwitcherModule from './intrapage-dot-switcher.js'; import * as liveMousePositionModule from './live-mouse-position.js'; +import * as memorableDetailsModule from './memorable-details.js'; import * as quickDescriptionModule from './quick-description.js'; +import * as revealAllGridControlModule from './reveal-all-grid-control.js'; import * as scriptedLinkModule from './scripted-link.js'; import * as sidebarSearchModule from './sidebar-search.js'; import * as stickyHeadingModule from './sticky-heading.js'; @@ -29,15 +32,20 @@ export const modules = [ artTagGalleryFilterModule, artTagNetworkModule, artistExternalLinkTooltipModule, + artistRollingWindowModule, cssCompatibilityAssistantModule, datetimestampTooltipModule, draggedLinkModule, + expandableGridSectionModule, + galleryStyleSelectorModule, hashLinkModule, hoverableTooltipModule, imageOverlayModule, intrapageDotSwitcherModule, liveMousePositionModule, + memorableDetailsModule, quickDescriptionModule, + revealAllGridControlModule, scriptedLinkModule, sidebarSearchModule, stickyHeadingModule, diff --git a/src/static/js/client/intrapage-dot-switcher.js b/src/static/js/client/intrapage-dot-switcher.js index d06bc5a6..b9a27a9b 100644 --- a/src/static/js/client/intrapage-dot-switcher.js +++ b/src/static/js/client/intrapage-dot-switcher.js @@ -1,5 +1,3 @@ -/* eslint-env browser */ - import {stitchArrays} from '../../shared-util/sugar.js'; import {cssProp} from '../client-util.js'; diff --git a/src/static/js/client/live-mouse-position.js b/src/static/js/client/live-mouse-position.js index 36a28429..32fc5bf4 100644 --- a/src/static/js/client/live-mouse-position.js +++ b/src/static/js/client/live-mouse-position.js @@ -1,5 +1,3 @@ -/* eslint-env browser */ - export const info = { id: 'liveMousePositionInfo', diff --git a/src/static/js/client/memorable-details.js b/src/static/js/client/memorable-details.js new file mode 100644 index 00000000..57d9fde8 --- /dev/null +++ b/src/static/js/client/memorable-details.js @@ -0,0 +1,62 @@ +import {stitchArrays} from '../../shared-util/sugar.js'; + +export const info = { + id: 'memorableDetailsInfo', + + details: null, + ids: null, + + session: { + openDetails: { + type: 'json', + maxLength: settings => settings.maxOpenDetailsStorage, + }, + }, + + settings: { + maxOpenDetailsStorage: 1000, + }, +}; + +export function getPageReferences() { + info.details = + Array.from(document.querySelectorAll('details.memorable')); + + info.ids = + info.details.map(details => details.getAttribute('data-memorable-id')); +} + +export function mutatePageContent() { + stitchArrays({ + details: info.details, + id: info.ids, + }).forEach(({details, id}) => { + if (info.session.openDetails?.includes(id)) { + details.open = true; + } + }); +} + +export function addPageListeners() { + for (const [index, details] of info.details.entries()) { + details.addEventListener('toggle', () => { + handleDetailsToggled(index); + }); + } +} + +function handleDetailsToggled(index) { + const details = info.details[index]; + const id = info.ids[index]; + + if (details.open) { + if (info.session.openDetails) { + info.session.openDetails = [...info.session.openDetails, id]; + } else { + info.session.openDetails = [id]; + } + } else if (info.session.openDetails?.includes(id)) { + info.session.openDetails = + info.session.openDetails.filter(item => item !== id); + } +} diff --git a/src/static/js/client/quick-description.js b/src/static/js/client/quick-description.js index cff82252..9117d48c 100644 --- a/src/static/js/client/quick-description.js +++ b/src/static/js/client/quick-description.js @@ -1,5 +1,3 @@ -/* eslint-env browser */ - import {stitchArrays} from '../../shared-util/sugar.js'; export const info = { diff --git a/src/static/js/client/reveal-all-grid-control.js b/src/static/js/client/reveal-all-grid-control.js new file mode 100644 index 00000000..0572a190 --- /dev/null +++ b/src/static/js/client/reveal-all-grid-control.js @@ -0,0 +1,70 @@ +import {cssProp} from '../client-util.js'; + +export const info = { + id: 'revealAllGridControlInfo', + + revealAllLinks: null, + revealables: null, + + revealLabels: null, + concealLabels: null, +}; + +export function getPageReferences() { + info.revealAllLinks = + Array.from(document.querySelectorAll('.reveal-all a')); + + info.revealables = + info.revealAllLinks + .map(link => link.closest('.grid-listing')) + .map(listing => listing.querySelectorAll('.reveal')); + + info.revealLabels = + info.revealAllLinks + .map(link => link.querySelector('.reveal-label')); + + info.concealLabels = + info.revealAllLinks + .map(link => link.querySelector('.conceal-label')); +} + +export function addPageListeners() { + for (const [index, link] of info.revealAllLinks.entries()) { + link.addEventListener('click', domEvent => { + domEvent.preventDefault(); + handleRevealAllLinkClicked(index); + }); + } +} + +export function addInternalListeners() { + // Don't even think about it. "Reveal all artworks" is a stable control, + // meaning it only changes because the user interacted with it directly. +} + +function handleRevealAllLinkClicked(index) { + const revealables = info.revealables[index]; + const revealLabel = info.revealLabels[index]; + const concealLabel = info.concealLabels[index]; + + const shouldReveal = + (cssProp(revealLabel, 'display') === 'none' + ? false + : true); + + for (const revealable of revealables) { + if (shouldReveal) { + revealable.classList.add('revealed'); + } else { + revealable.classList.remove('revealed'); + } + } + + if (shouldReveal) { + cssProp(revealLabel, 'display', 'none'); + cssProp(concealLabel, 'display', null); + } else { + cssProp(revealLabel, 'display', null); + cssProp(concealLabel, 'display', 'none'); + } +} diff --git a/src/static/js/client/scripted-link.js b/src/static/js/client/scripted-link.js index 8b8d8a13..badc6ccb 100644 --- a/src/static/js/client/scripted-link.js +++ b/src/static/js/client/scripted-link.js @@ -1,5 +1,3 @@ -/* eslint-env browser */ - import {pick, stitchArrays} from '../../shared-util/sugar.js'; import { diff --git a/src/static/js/client/sidebar-search.js b/src/static/js/client/sidebar-search.js index c8f42e91..b4356a0f 100644 --- a/src/static/js/client/sidebar-search.js +++ b/src/static/js/client/sidebar-search.js @@ -1,5 +1,3 @@ -/* eslint-env browser */ - import {getColors} from '../../shared-util/colors.js'; import {accumulateSum, empty, unique} from '../../shared-util/sugar.js'; @@ -73,6 +71,10 @@ export const info = { groupResultKindString: null, tagResultKindString: null, + groupResultDisambiguatorString: null, + flashResultDisambiguatorString: null, + trackResultDisambiguatorString: null, + albumResultFilterString: null, artistResultFilterString: null, flashResultFilterString: null, @@ -196,6 +198,15 @@ export function getPageReferences() { info.tagResultKindString = findString('tag-result-kind'); + info.groupResultDisambiguatorString = + findString('group-result-disambiguator'); + + info.flashResultDisambiguatorString = + findString('flash-result-disambiguator'); + + info.trackResultDisambiguatorString = + findString('track-result-disambiguator'); + info.albumResultFilterString = findString('album-result-filter'); @@ -841,7 +852,7 @@ function fillResultElements(results, { } for (const result of filteredResults) { - const el = generateSidebarSearchResult(result); + const el = generateSidebarSearchResult(result, filteredResults); if (!el) continue; info.results.appendChild(el); @@ -890,13 +901,13 @@ function showFilterElements(results) { } } -function generateSidebarSearchResult(result) { +function generateSidebarSearchResult(result, results) { const preparedSlots = { color: result.data.color ?? null, name: - result.data.name ?? result.data.primaryName ?? null, + getSearchResultName(result), imageSource: getSearchResultImageSource(result), @@ -961,9 +972,37 @@ function generateSidebarSearchResult(result) { return null; } + const compareReferenceType = otherResult => + otherResult.referenceType === result.referenceType; + + const compareName = otherResult => + getSearchResultName(otherResult) === getSearchResultName(result); + + const ambiguous = + results.some(otherResult => + otherResult !== result && + compareReferenceType(otherResult) && + compareName(otherResult)); + + if (ambiguous) { + preparedSlots.disambiguate = + result.data.disambiguator; + + preparedSlots.disambiguatorString = + info[result.referenceType + 'ResultDisambiguatorString']; + } + return generateSidebarSearchResultTemplate(preparedSlots); } +function getSearchResultName(result) { + return ( + result.data.name ?? + result.data.primaryName ?? + null + ); +} + function getSearchResultImageSource(result) { const {artwork} = result.data; if (!artwork) return null; @@ -1039,6 +1078,15 @@ function generateSidebarSearchResultTemplate(slots) { } } + if (!accentSpan && slots.disambiguate) { + accentSpan = document.createElement('span'); + accentSpan.classList.add('wiki-search-result-disambiguator'); + accentSpan.appendChild( + templateContent(slots.disambiguatorString, { + disambiguator: slots.disambiguate, + })); + } + if (!accentSpan && slots.kindString) { accentSpan = document.createElement('span'); accentSpan.classList.add('wiki-search-result-kind'); @@ -1305,7 +1353,7 @@ async function handleDroppedIntoSearchInput(domEvent) { let droppedURL; try { droppedURL = new URL(droppedText); - } catch (error) { + } catch { droppedURL = null; } diff --git a/src/static/js/client/sticky-heading.js b/src/static/js/client/sticky-heading.js index b65574d0..c69e137f 100644 --- a/src/static/js/client/sticky-heading.js +++ b/src/static/js/client/sticky-heading.js @@ -1,5 +1,3 @@ -/* eslint-env browser */ - import {filterMultipleArrays, stitchArrays} from '../../shared-util/sugar.js'; import {cssProp, dispatchInternalEvent, templateContent} from '../client-util.js'; @@ -255,7 +253,11 @@ function getContentHeadingClosestToStickySubheading(index) { // Iterate from bottom to top of the content area. const contentHeadings = info.contentHeadings[index]; - for (const heading of contentHeadings.slice().reverse()) { + for (const heading of contentHeadings.toReversed()) { + if (heading.nodeName === 'SUMMARY' && !heading.closest('details').open) { + continue; + } + const headingRect = heading.getBoundingClientRect(); if (headingRect.y + headingRect.height / 1.5 < stickyBottom + 40) { return heading; diff --git a/src/static/js/client/summary-nested-link.js b/src/static/js/client/summary-nested-link.js index 23857fa5..1c4e7e4b 100644 --- a/src/static/js/client/summary-nested-link.js +++ b/src/static/js/client/summary-nested-link.js @@ -1,5 +1,3 @@ -/* eslint-env browser */ - import { empty, filterMultipleArrays, diff --git a/src/static/js/client/text-with-tooltip.js b/src/static/js/client/text-with-tooltip.js index dd207e04..2b855756 100644 --- a/src/static/js/client/text-with-tooltip.js +++ b/src/static/js/client/text-with-tooltip.js @@ -1,5 +1,3 @@ -/* eslint-env browser */ - import {stitchArrays} from '../../shared-util/sugar.js'; import {registerTooltipElement, registerTooltipHoverableElement} diff --git a/src/static/js/client/wiki-search.js b/src/static/js/client/wiki-search.js index 2446c172..9a6e29c1 100644 --- a/src/static/js/client/wiki-search.js +++ b/src/static/js/client/wiki-search.js @@ -1,5 +1,3 @@ -/* eslint-env browser */ - import {promiseWithResolvers} from '../../shared-util/sugar.js'; import {dispatchInternalEvent} from '../client-util.js'; diff --git a/src/static/js/group-contributions-table.js b/src/static/js/group-contributions-table.js index 72ad2327..bef85fad 100644 --- a/src/static/js/group-contributions-table.js +++ b/src/static/js/group-contributions-table.js @@ -1,5 +1,3 @@ -/* eslint-env browser */ - // TODO: Update to clientSteps style. const groupContributionsTableInfo = diff --git a/src/static/js/info-card.js b/src/static/js/info-card.js index 1d9f7c86..05d5d801 100644 --- a/src/static/js/info-card.js +++ b/src/static/js/info-card.js @@ -1,5 +1,3 @@ -/* eslint-env browser */ - // Note: This is a super ancient chunk of code which isn't actually in use, // so it's just commented out here. diff --git a/src/static/js/lazy-loading.js b/src/static/js/lazy-loading.js index 1df56f08..0c8aef31 100644 --- a/src/static/js/lazy-loading.js +++ b/src/static/js/lazy-loading.js @@ -1,5 +1,3 @@ -/* eslint-env browser */ - // Lazy loading! Roll your own. Woot. // This file includes a 8unch of fall8acks and stuff like that, and is written // with fairly Olden JavaScript(TM), so as to work on pretty much any 8rowser diff --git a/src/static/js/rectangles.js b/src/static/js/rectangles.js index b00ed98e..24382ef8 100644 --- a/src/static/js/rectangles.js +++ b/src/static/js/rectangles.js @@ -1,5 +1,3 @@ -/* eslint-env browser */ - import {info as liveMousePositionInfo} from './client/live-mouse-position.js'; export class WikiRect extends DOMRect { diff --git a/src/static/js/search-worker.js b/src/static/js/search-worker.js index c3002b18..96ad8a0b 100644 --- a/src/static/js/search-worker.js +++ b/src/static/js/search-worker.js @@ -1,8 +1,7 @@ -/* eslint-env worker */ - import FlexSearch from '../lib/flexsearch/flexsearch.bundle.module.min.js'; -import {makeSearchIndex, searchSpec} from '../shared-util/search-spec.js'; +import {default as searchSpec, makeSearchIndex} + from '../shared-util/search-shape.js'; import { empty, @@ -130,7 +129,7 @@ async function loadDatabase() { try { idb = await promisifyIDBRequest(request); - } catch (error) { + } catch { console.warn(`Couldn't load search IndexedDB - won't use an internal cache.`); console.warn(request.error); idb = null; @@ -391,6 +390,8 @@ function performSearchAction({query, options}) { } const interestingFieldCombinations = [ + ['primaryName'], + ['primaryName', 'parentName', 'groups'], ['primaryName', 'parentName'], ['primaryName', 'groups', 'contributors'], @@ -412,7 +413,6 @@ const interestingFieldCombinations = [ ['contributors', 'parentName'], ['contributors', 'groups'], ['primaryName', 'contributors'], - ['primaryName'], ]; function queryGenericIndex(query, options) { diff --git a/src/static/js/xhr-util.js b/src/static/js/xhr-util.js index 8a43072c..bc0698da 100644 --- a/src/static/js/xhr-util.js +++ b/src/static/js/xhr-util.js @@ -1,5 +1,3 @@ -/* eslint-env browser */ - /** * This fetch function is adapted from a `loadImage` function * credited to Parziphal, Feb 13, 2017. diff --git a/src/strings-default.yaml b/src/strings-default.yaml index ef885e00..85d849db 100644 --- a/src/strings-default.yaml +++ b/src/strings-default.yaml @@ -97,16 +97,6 @@ count: many: "" other: "{CONTRIBUTIONS} contributions" - coverArts: - _: "{COVER_ARTS}" - withUnit: - zero: "" - one: "{COVER_ARTS} cover art" - two: "" - few: "" - many: "" - other: "{COVER_ARTS} cover arts" - flashes: _: "{FLASHES}" withUnit: @@ -269,9 +259,14 @@ releaseInfo: # Descriptions - by: - _: "By {ARTISTS}." - featuring: "By {ARTISTS}, featuring {FEATURING}." + by: >- + By {ARTISTS}. + by.featuring: >- + By {ARTISTS}, featuring {FEATURING}. + by.withAlbum: >- + From {ALBUM}, by {ARTISTS}. + by.featuring.withAlbum: >- + From {ALBUM}, by {ARTISTS} feat. {FEATURING}. from: "From {ALBUM}." @@ -294,7 +289,22 @@ releaseInfo: note: "Context notes:" - alsoReleasedOn: "Also released on {ALBUMS}." + alsoReleased: + onAlbums: >- + Also released on {ALBUMS}. + + asSingle: >- + Also released {SINGLE}. + + onAlbums.asSingle: >- + Also released on {ALBUMS}, and {SINGLE}. + + single: "as a single" + + previousProduction: + _: "This track is a previous version or production of {TRACKS}." + + trackOnAlbum: "{TRACK} (on {ALBUM})" tracksReferenced: _: "Tracks that {TRACK} references:" @@ -368,12 +378,20 @@ releaseInfo: readCommentary: _: "Read {LINK}." - link: "artist commentary" - readCreditSources: + link: + _: "artist commentary" + withWikiCommentary: "artist and wiki commentary" + onlyWikiCommentary: "wiki commentary" + + readCreditingSources: _: "Read {LINK}." link: "crediting sources" + readReferencingSources: + _: "Read {LINK}." + link: "referencing sources" + additionalFiles: heading: "View or download additional files:" @@ -449,6 +467,9 @@ trackList: rerelease: >- {TRACK} (rerelease) + previousProduction: >- + {TRACK} (previous version or production) + # # misc: # @@ -488,7 +509,16 @@ misc: # artistCommentary: artistCommentary: - _: "Artist commentary:" + _: "Artist commentary for {THING}:" + sticky: "Artist commentary:" + + withWikiCommentary: + _: "Artist and wiki commentary for {THING}:" + sticky: "Artist and wiki commentary:" + + onlyWikiCommentary: + _: "Wiki commentary for {THING}:" + sticky: "Wiki commentary:" entry: title: @@ -515,13 +545,10 @@ misc: info: fromMainRelease: >- - This commentary is properly placed on this track's main release, {ALBUM}. + The following commentary is properly placed on this track's main release, {ALBUM}. fromMainRelease.namedDifferently: >- - This commentary is properly placed on this track's main release, {ALBUM}, where it's named {NAME}. - - releaseSpecific: >- - This commentary is specific to this release, {ALBUM}. + The following commentary is properly placed on this track's main release, {ALBUM}, where it's named {NAME}. seeSpecificReleases: >- For release-specific commentary, check out: {ALBUMS}. @@ -562,6 +589,10 @@ misc: noExternalLinkPlatformName: "Other" chronology: + heading: + artistReleases: "Releases by {ARTIST}:" + artistTracks: "Tracks by {ARTIST}:" + previous: symbol: "←" info: @@ -579,47 +610,17 @@ misc: bannerArt: "banner art" coverArt: "cover art" flash: "flash" + release: "release" track: "track" trackArt: "track art" trackContribution: "track contribution" wallpaperArt: "wallpaper art" - # chronology: - # - # "Chronology links" are a section that appear in the nav bar for - # most things with individual contributors across the wiki! These - # allow for quick navigation between older and newer releases of - # a given artist, or seeing at a glance how many contributions an - # artist made before the one you're currently viewing. - # - # Chronology information is described for each artist and shows - # the kind of thing which is being contributed to, since all of - # the entries are displayed together in one list. - # - - chronology: - - # seeArtistPages: - # If the thing you're viewing has a lot of contributors, their - # chronology info will be exempt from the nav bar, which'll - # show this message instead. - - seeArtistPages: "(See artist pages for chronology info!)" - - # withNavigation: - # Navigation refers to previous/next links. - - withNavigation: "{HEADING} ({NAVIGATION})" - - heading: - coverArt: "{INDEX} cover art by {ARTIST}" - flash: "{INDEX} flash/game by {ARTIST}" - track: "{INDEX} track by {ARTIST}" - trackArt: "{INDEX} track art by {ARTIST}" - onlyIndex: "Only" - - creditSources: - _: "Crediting sources:" + creditingSources: + _: "{CUE} for {THING}:" + collapsed: "{CUE} for {THING}…" + cue: "Crediting sources" + sticky: "Crediting sources:" # external: # Links which will generally bring you somewhere off of the wiki. @@ -647,7 +648,12 @@ misc: amazonMusic: "Amazon Music" appleMusic: "Apple Music" artstation: "ArtStation" - bandcamp: "Bandcamp" + + bandcamp: + _: "Bandcamp" + + composerRelease: "Bandcamp (composer's release)" + officialRelease: "Bandcamp (official release)" bgreco: _: "bgreco.net" @@ -694,6 +700,11 @@ misc: nintendoMusic: "Nintendo Music" patreon: "Patreon" poetryFoundation: "Poetry Foundation" + + reddit: + _: "Reddit" + subreddit: "Reddit ({SUBREDDIT})" + soundcloud: "SoundCloud" spotify: "Spotify" steam: "Steam" @@ -711,6 +722,18 @@ misc: playlist: "YouTube (playlist)" fullAlbum: "YouTube (full album)" + # lyrics: + + lyrics: + source: >- + Via {SOURCE} + + contributors: >- + Contributions from {CONTRIBUTORS} + + squareBracketAnnotations: >- + Mind parts marked in [square brackets] + # missingImage: # Fallback text displayed in an image when it's sourced to a file # that isn't available under the wiki's media directory. While it @@ -769,8 +792,10 @@ misc: nav: previous: "Previous" next: "Next" + info: "Info" gallery: "Gallery" + rollingWindow: "Rolling Window" # pageTitle: # Title set under the page's <title> HTML element, which is @@ -818,6 +843,11 @@ misc: artist: "(artist)" group: "(group)" + resultDisambiguator: + group: "({DISAMBIGUATOR})" + flash: "(in {DISAMBIGUATOR})" + track: "(from {DISAMBIGUATOR})" + resultFilter: album: "Albums" artTag: "Art Tags" @@ -826,6 +856,12 @@ misc: group: "Groups" track: "Tracks" + referencingSources: + _: "{CUE} for {THING}:" + collapsed: "{CUE} for {THING}…" + cue: "Referencing sources" + sticky: "Referencing sources:" + # skippers: # # These are navigational links that only show up when you're @@ -853,7 +889,7 @@ misc: # Displayed on various info pages. artistCommentary: "Artist commentary" - creditSources: "Crediting sources" + creditingSources: "Crediting sources" # Displayed on artist info page. @@ -874,6 +910,7 @@ misc: sampledBy: "Sampled by..." features: "Features..." featuredIn: "Featured in..." + referencingSources: "Referencing sources" lyrics: "Lyrics" @@ -958,12 +995,30 @@ misc: # that thing. coverGrid: + revealAll: + reveal: "Reveal all artworks" + conceal: "Conceal all artworks" + warnings: "In this gallery: {WARNINGS}" + + expandCollapseCue: "({CUE})" + expand: "Show the rest!" + collapse: "Collapse these" + noCoverArt: "{ALBUM}" + tab: + groups: "{GROUPS}" + artists: "{ARTISTS}" + artists.featuring: "{ARTISTS} feat. {FEATURING}" + details: + notFromThisGroup: "{NAME}{MARKER}" + notFromThisGroup.marker: "*" + accent: "({DETAILS})" albumLength: "{TRACKS}, {TIME}" + albumLength.single: "single, {TIME}" coverArtists: "Artwork by {ARTISTS}" coverArtists.customLabel: "{LABEL} by {ARTISTS}" @@ -1129,6 +1184,9 @@ albumGalleryPage: statsLine.withDate: >- {TRACKS} totaling {DURATION}. Released {DATE}. + statsLine.withDate.noDuration: >- + Released {DATE}. + # coverArtistsLine: # This is displayed if every track (which has artwork at all) # has the same illustration credits. @@ -1305,6 +1363,16 @@ artistPage: flash: _: "{FLASH}" + artwork.accent: + withLabel: >- + {LABEL} + + withAnnotation: >- + {ANNOTATION} + + withLabel.withAnnotation: >- + {LABEL}: {ANNOTATION} + # contributedDurationLine: # This is shown at the top of the artist's track list, provided # any of their tracks have durations at all. @@ -1377,6 +1445,41 @@ artistGalleryPage: infoLine: >- Contributed to {COVER_ARTS}. +artistRollingWindowPage: + title: "{ARTIST} - Rolling Window" + + windowConfigurationLine: >- + With a rolling window of {TIME_BEFORE} before a given date, and {TIME_AFTER} after, peeking ahead {PEEK}... + + contributionConfigurationLine: >- + Selecting {KIND} contributions from group {GROUP}... + + timeframeSelectionLine: + _: >- + There are {CONTRIBUTIONS} contributions, making {TIMEFRAMES} timeframes between {FIRST_DATE} and {LAST_DATE}. + none: >- + There aren't any matching contributions, or those which do aren't dated, so there are no timeframes. + + emptyTimeframeLine: >- + This timeframe is empty, since no contributions are in range. + + timeframeSelectionControl: + _: "{PREVIOUS} {TIMEFRAMES} {NEXT}" + previous: "← Previous" + next: "Next →" + + contributionKind: + artwork: "Artwork" + music: "Music" + flash: "Flash" + + contributionGroup: + all: "All groups" + group: "{GROUP}" + + timeframe: + months: "{INPUT} months" + # # artTagPage: # Stuff that's common between art tag pages. @@ -1653,6 +1756,42 @@ groupGalleryPage: infoLine: >- {TRACKS} across {ALBUMS}, totaling {TIME}. + albumViewSwitcher: + _: "Showing albums:" + + bySeries: "By series" + byDate: "By date" + + albumStyleSwitcher: + _: "Showing these releases:" + + album: "Albums" + single: "Singles" + + count: + all: "all {TOTAL}" + filtered: "{COUNT} of {TOTAL}" + none: "none at all" + + albumsByDate: + title: "All albums" + + albumSection: + caption: >- + {TRACKS} across {ALBUMS}. + + caption.withDate: >- + {TRACKS} across {ALBUMS}, released {DATE}. + + caption.withYear: >- + {TRACKS} across {ALBUMS}, released during {YEAR}. + + caption.withYearRange: >- + {TRACKS} across {ALBUMS}, released {YEAR_RANGE}. + + caption.seriesAlbumsNotFromGroup: >- + Albums marked {MARKER} are part of {SERIES}, but not from {GROUP}. + # # listingIndex: # The listing index page shows all available listings on the wiki, @@ -1744,6 +1883,7 @@ listingPage: title: "Albums - by Tracks" title.short: "...by Tracks" item: "{ALBUM} ({TRACKS})" + item.single: "{ALBUM} ({TRACKS}—single)" # listAlbums.byDuration: # Lists albums by total duration of all tracks, longest to @@ -2180,6 +2320,23 @@ listingPage: title.withDate: "{ALBUM} ({DATE})" item: "{TRACK}" + # listTracks.needingLyrics: + # List tracks, chunked by album (which are sorted by date, + # falling back alphabetically) and in their usual track order, + # displaying only tracks which are marked as needing lyrics. + # The chunk titles also display the date each album was released, + # and tracks' own custom "Date First Released" fields are totally + # ignored. + + needingLyrics: + title: "Tracks - which need Lyrics" + title.short: "...which need Lyrics" + + chunk: + title: "{ALBUM}" + title.withDate: "{ALBUM} ({DATE})" + item: "{TRACK}" + other: # other.allSheetMusic: @@ -2391,15 +2548,14 @@ trackPage: backToTrack: "Return to track page" + singleAccent: "single" + track: _: "{TRACK}" withNumber: "{NUMBER}. {TRACK}" - chronology: - scope: - title: "Chronology links {SCOPE}" - wiki: "across this wiki" - album: "within this album" + needsLyrics: >- + This track has vocals, but there aren't lyrics available for it on this wiki, yet! socialEmbed: heading: "{ALBUM}" diff --git a/src/upd8.js b/src/upd8.js index 86ecab69..ae072d5a 100755 --- a/src/upd8.js +++ b/src/upd8.js @@ -42,8 +42,9 @@ import wrap from 'word-wrap'; import {mapAggregate, openAggregate, showAggregate} from '#aggregate'; import CacheableObject from '#cacheable-object'; -import {stringifyCache} from '#cli'; +import {formatDuration, stringifyCache} from '#cli'; import {displayCompositeCacheAnalysis} from '#composite'; +import * as html from '#html'; import find, {bindFind, getAllFindSpecs} from '#find'; import {processLanguageFile, watchLanguageFile, internalDefaultStringsFile} from '#language'; @@ -117,7 +118,7 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url)); let COMMIT; try { COMMIT = execSync('git log --format="%h %B" -n 1 HEAD', {cwd: __dirname}).toString().trim(); -} catch (error) { +} catch { COMMIT = '(failed to detect)'; } @@ -518,6 +519,11 @@ async function main() { type: 'flag', }, + 'skip-self-diagnosis': { + help: `Disable some runtime validation for the wiki's own code, which speeds up long builds, but may allow unpredicted corner cases to fail strangely and silently`, + type: 'flag', + }, + 'queue-size': { help: `Process more or fewer disk files at once to optimize performance or avoid I/O errors, unlimited if set to 0 (between 500 and 700 is usually a safe range for building HSMusic on Windows machines)\nDefaults to ${defaultQueueSize}`, type: 'value', @@ -656,7 +662,8 @@ async function main() { const thumbsOnly = cliOptions['thumbs-only'] ?? false; const noInput = cliOptions['no-input'] ?? false; - const showAggregateTraces = cliOptions['show-traces'] ?? false; + const skipSelfDiagnosis = cliOptions['skip-self-diagnosis'] ?? false; + const showTraces = cliOptions['show-traces'] ?? false; const precacheMode = cliOptions['precache-mode'] ?? 'common'; @@ -1156,6 +1163,18 @@ async function main() { return false; } + if (skipSelfDiagnosis) { + logWarn`${'Skipping code self-diagnosis.'} (--skip-self-diagnosis provided)`; + logWarn`This build should run substantially faster, but corner cases`; + logWarn`not previously predicted may fail strangely and silently.`; + + html.disableSlotValidation(); + } + + if (!showTraces) { + html.disableTagTracing(); + } + Object.assign(stepStatusSummary.determineMediaCachePath, { status: STATUS_STARTED_NOT_DONE, timeStart: Date.now(), @@ -1334,7 +1353,7 @@ async function main() { const niceShowAggregate = (error, ...opts) => { showAggregate(error, { - showTraces: showAggregateTraces, + showTraces, pathToFileURL: (f) => path.relative(__dirname, fileURLToPath(f)), ...opts, }); @@ -2419,7 +2438,7 @@ async function main() { }); internalDefaultLanguage = internalDefaultLanguageWatcher.language; - } catch (_error) { + } catch { // No need to display the error here - it's already printed by // watchLanguageFile. errorLoadingInternalDefaultLanguage = true; @@ -3207,6 +3226,7 @@ async function main() { developersComment, languages, missingImagePaths, + niceShowAggregate, thumbsCache, urlSpec, urls, @@ -3269,7 +3289,7 @@ async function main() { } // TODO: isMain detection isn't consistent across platforms here -/* eslint-disable-next-line no-constant-condition */ +// eslint-disable-next-line no-constant-binary-expression if (true || isMain(import.meta.url) || path.basename(process.argv[1]) === 'hsmusic') { (async () => { let result; @@ -3363,23 +3383,6 @@ if (true || isMain(import.meta.url) || path.basename(process.argv[1]) === 'hsmus })(); } -function formatDuration(timeDelta) { - const seconds = timeDelta / 1000; - - if (seconds > 90) { - const modSeconds = Math.floor(seconds % 60); - const minutes = Math.floor(seconds - seconds % 60) / 60; - return `${minutes}m${modSeconds}s`; - } - - if (seconds < 0.1) { - return 'instant'; - } - - const precision = (seconds > 1 ? 3 : 2); - return `${seconds.toPrecision(precision)}s`; -} - function showStepStatusSummary() { const longestNameLength = Math.max(... diff --git a/src/url-spec.js b/src/url-spec.js index 75cd8006..2e8b9fc1 100644 --- a/src/url-spec.js +++ b/src/url-spec.js @@ -1,6 +1,7 @@ // Exports defined here are re-exported through urls.js, // so they're generally imported from '#urls'. +import {readFileSync} from 'node:fs'; import {readFile} from 'node:fs/promises'; import * as path from 'node:path'; import {fileURLToPath} from 'node:url'; @@ -195,6 +196,24 @@ export async function processURLSpecFromFile(file) { error => annotateErrorWithFile(error, file)); } + return processURLSpecFromFileContents(file, contents); +} + +export function processURLSpecFromFileSync(file) { + let contents; + + try { + contents = readFileSync(file, 'utf-8'); + } catch (caughtError) { + throw annotateError( + new Error(`Failed to read URL spec file`, {cause: caughtError}), + error => annotateErrorWithFile(error, file)); + } + + return processURLSpecFromFileContents(file, contents); +} + +function processURLSpecFromFileContents(file, contents) { let sourceSpec; let parseLanguage; diff --git a/src/urls-default.yaml b/src/urls-default.yaml index 74225efd..667f7d8b 100644 --- a/src/urls-default.yaml +++ b/src/urls-default.yaml @@ -11,7 +11,7 @@ yamlAliases: # part of a build. This is so that multiple builds of a wiki can coexist # served from the same server / file system root: older builds' HTML files # refer to earlier values of STATIC_VERSION, avoiding name collisions. - - &staticVersion 5p1 + - &staticVersion 5p2 data: prefix: 'data/' @@ -41,6 +41,7 @@ localized: artist: 'artist/<>/' artistGallery: 'artist/<>/gallery/' + artistRollingWindow: 'artist/<>/rolling-window/' commentaryIndex: 'commentary/' diff --git a/src/urls.js b/src/urls.js index 9cc4a554..b51ea459 100644 --- a/src/urls.js +++ b/src/urls.js @@ -129,6 +129,9 @@ export function generateURLs(urlSpec) { if (template === null) { // Self-diagnose, brutally. + // TODO: This variable isn't used, and has never been used, + // but it might have been *meant* to be used? + // eslint-disable-next-line no-unused-vars const otherTemplateKey = (device ? 'posix' : 'device'); const {value: {[templateKey]: otherTemplate}} = diff --git a/src/validators.js b/src/validators.js index 5b8227fb..63268ded 100644 --- a/src/validators.js +++ b/src/validators.js @@ -738,14 +738,6 @@ export const isContributionPreset = validateProperties({ export const isContributionPresetList = validateArrayItems(isContributionPreset); -export const isAdditionalFile = validateProperties({ - title: isName, - description: optional(isContentString), - files: optional(validateArrayItems(isString)), -}); - -export const isAdditionalFileList = validateArrayItems(isAdditionalFile); - export const isTrackSection = validateProperties({ name: optional(isName), color: optional(isColor), @@ -756,17 +748,6 @@ export const isTrackSection = validateProperties({ export const isTrackSectionList = validateArrayItems(isTrackSection); -export const isSeries = validateProperties({ - name: isName, - description: optional(isContentString), - albums: optional(validateReferenceList('album')), - - showAlbumArtists: - optional(is('all', 'differing', 'none')), -}); - -export const isSeriesList = validateArrayItems(isSeries); - export const isWallpaperPart = validateProperties({ asset: optional(isString), style: optional(isString), @@ -861,9 +842,11 @@ export function validateReference(type) { type.map(type => `"${type}:"`).join(', ') + `, got "${typePart}:"`); } - } else if (typePart !== type) { - throw new TypeError( - `Expected ref to begin with "${type}:", got "${typePart}:"`); + } else if (type) { + if (typePart !== type) { + throw new TypeError( + `Expected ref to begin with "${type}:", got "${typePart}:"`); + } } isDirectory(directoryPart); @@ -987,13 +970,6 @@ export function validateWikiData({ }; } -export const isAdditionalName = validateProperties({ - name: isContentString, - annotation: optional(isContentString), -}); - -export const isAdditionalNameList = validateArrayItems(isAdditionalName); - // Compositional utilities export function anyOf(...validators) { diff --git a/src/web-routes.js b/src/web-routes.js index b68dccbf..a7115bbd 100644 --- a/src/web-routes.js +++ b/src/web-routes.js @@ -4,8 +4,10 @@ import {fileURLToPath} from 'node:url'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); +/* eslint-disable no-unused-vars */ const codeSrcPath = __dirname; const codeRootPath = path.resolve(codeSrcPath, '..'); +/* eslint-enable no-unused-vars */ function getNodeDependencyRootPath(dependencyName) { return ( diff --git a/src/write/bind-utilities.js b/src/write/bind-utilities.js index d55ab215..afbf8b2f 100644 --- a/src/write/bind-utilities.js +++ b/src/write/bind-utilities.js @@ -24,6 +24,7 @@ export function bindUtilities({ language, languages, missingImagePaths, + niceShowAggregate, pagePath, pagePathStringFromRoot, thumbsCache, @@ -42,6 +43,7 @@ export function bindUtilities({ language, languages, missingImagePaths, + niceShowAggregate, pagePath, pagePathStringFromRoot, thumb, diff --git a/src/write/build-modes/live-dev-server.js b/src/write/build-modes/live-dev-server.js index ecb9df21..5dece8d0 100644 --- a/src/write/build-modes/live-dev-server.js +++ b/src/write/build-modes/live-dev-server.js @@ -253,7 +253,7 @@ export async function go({ let url; try { url = new URL(request.url, `http://${request.headers.host}`); - } catch (error) { + } catch { response.writeHead(500, contentTypePlain); response.end('Failed to parse request URL\n'); return; @@ -300,7 +300,7 @@ export async function go({ let filePath; try { filePath = path.resolve(localDirectory, decodeURI(safePath.split('/').join(path.sep))); - } catch (error) { + } catch { response.writeHead(404, contentTypePlain); response.end(`File not found for: ${safePath}`); console.log(`${requestHead} [404] ${pathname}`); diff --git a/src/write/build-modes/static-build.js b/src/write/build-modes/static-build.js index d363811d..89450fc2 100644 --- a/src/write/build-modes/static-build.js +++ b/src/write/build-modes/static-build.js @@ -1,6 +1,8 @@ -import {cp, mkdir, stat, symlink, writeFile, unlink} from 'node:fs/promises'; import * as path from 'node:path'; +import {cp, mkdir, readFile, stat, symlink, writeFile, unlink} + from 'node:fs/promises'; + import {rimraf} from 'rimraf'; import {quickLoadContentDependencies} from '#content-dependencies'; @@ -86,6 +88,11 @@ export function getCLIOptions() { type: 'value', }, + 'paths': { + help: `Skip rest and build only pages matching paths in this text file`, + type: 'value', + }, + // NOT for neatly ena8ling or disa8ling specific features of the site! // This is only in charge of what general groups of files to write. // They're here to make development quicker when you're only working @@ -106,8 +113,6 @@ export async function go({ universalUtilities, - mediaPath, - defaultLanguage, languages, urls, @@ -119,6 +124,7 @@ export async function go({ const outputPath = cliOptions['out-path'] || process.env.HSMUSIC_OUT; const appendIndexHTML = cliOptions['append-index-html'] ?? false; const writeOneLanguage = cliOptions['lang'] ?? null; + const pathsFromFile = cliOptions['paths'] ?? null; if (!outputPath) { logError`Expected ${'--out-path'} option or ${'HSMUSIC_OUT'} to be set`; @@ -138,6 +144,36 @@ export async function go({ logInfo`Writing all languages.`; } + let filterPaths = null; + if (pathsFromFile) { + let pathsText; + try { + pathsText = await readFile(pathsFromFile, 'utf8'); + } catch (error) { + logError`Failed to read file specified in ${'--paths'}:`; + logError`${error.code}: ${pathsFromFile}`; + return false; + } + + filterPaths = pathsText.split('\n').filter(Boolean); + + if (empty(filterPaths)) { + logWarn`Specified to build only paths in file ${'--paths'}:`; + logWarn`${pathsFromFile}`; + logWarn`But this file is totally empty...`; + } + + if (filterPaths.some(path => !path.startsWith('/'))) { + logError`All lines in ${'--paths'} file should start with slash ('${'/'}')`; + logError`These lines don't:`; + console.error(filterPaths.filter(path => !path.startsWith('/')).join('\n')); + logError`Please check file contents, or specified path, and try again.`; + return false; + } + + logInfo`Writing ${filterPaths.length} paths specified in: ${pathsFromFile} (${'--paths'})`; + } + const selectedPageFlags = Object.keys(cliOptions) .filter(key => pageFlags.includes(key)); @@ -211,6 +247,27 @@ export async function go({ // TODO: Validate each pathsForTargets entry } + if (!empty(filterPaths)) { + paths = + paths.filter(path => + filterPaths.includes( + (path.type === 'page' + ? '/' + + getPagePathname({ + baseDirectory: '', + pagePath: path.path, + urls, + }) + : path.type === 'redirect' + ? '/' + + getPagePathname({ + baseDirectory: '', + pagePath: path.fromPath, + urls, + }) + : null))); + } + paths = paths.filter(path => path.condition?.() ?? true); diff --git a/tap-snapshots/test/snapshot/generateAlbumAdditionalFilesList.js.test.cjs b/tap-snapshots/test/snapshot/generateAlbumAdditionalFilesList.js.test.cjs deleted file mode 100644 index 4f09569d..00000000 --- a/tap-snapshots/test/snapshot/generateAlbumAdditionalFilesList.js.test.cjs +++ /dev/null @@ -1,56 +0,0 @@ -/* IMPORTANT - * This snapshot file is auto-generated, but designed for humans. - * It should be checked into source control and tracked carefully. - * Re-generate by setting TAP_SNAPSHOT=1 and running tests. - * Make sure to inspect the output below. Do not ignore changes! - */ -'use strict' -exports[`test/snapshot/generateAlbumAdditionalFilesList.js > TAP > generateAlbumAdditionalFilesList (snapshot) > basic behavior 1`] = ` -<ul class="additional-files-list"> - <li> - <details> - <summary><span><span class="group-name">SBURB Wallpaper</span></span></summary> - <ul> - <li><a href="media/album-additional/exciting-album/sburbwp_1280x1024.jpg">sburbwp_1280x1024.jpg</a></li> - <li><a href="media/album-additional/exciting-album/sburbwp_1440x900.jpg">sburbwp_1440x900.jpg</a></li> - <li><a href="media/album-additional/exciting-album/sburbwp_1920x1080.jpg">sburbwp_1920x1080.jpg</a></li> - </ul> - </details> - </li> - <li> - <details> - <summary><span><span class="group-name">Fake Section</span></span></summary> - <ul> - <li class="entry-description">No sizes for these files</li> - <li><a href="media/album-additional/exciting-album/oops.mp3">oops.mp3</a></li> - <li><a href="media/album-additional/exciting-album/Internet%20Explorer.gif">Internet Explorer.gif</a></li> - <li><a href="media/album-additional/exciting-album/daisy.mp3">daisy.mp3</a></li> - </ul> - </details> - </li> - <li> - <details open> - <summary><span><span class="group-name">Empty Section</span></span></summary> - <ul> - <li class="entry-description">These files haven't been made available.</li> - <li>There are no files available or listed for this entry.</li> - </ul> - </details> - </li> - <li> - <details> - <summary><span><span class="group-name">Alternate Covers</span></span></summary> - <ul> - <li class="entry-description">This is just an example description.</li> - <li><a href="media/album-additional/exciting-album/Homestuck_Vol4_alt1.jpg">Homestuck_Vol4_alt1.jpg</a></li> - <li><a href="media/album-additional/exciting-album/Homestuck_Vol4_alt2.jpg">Homestuck_Vol4_alt2.jpg</a></li> - <li><a href="media/album-additional/exciting-album/Homestuck_Vol4_alt3.jpg">Homestuck_Vol4_alt3.jpg</a></li> - </ul> - </details> - </li> -</ul> -` - -exports[`test/snapshot/generateAlbumAdditionalFilesList.js > TAP > generateAlbumAdditionalFilesList (snapshot) > no additional files 1`] = ` - -` diff --git a/tap-snapshots/test/snapshot/generateAlbumBanner.js.test.cjs b/tap-snapshots/test/snapshot/generateAlbumBanner.js.test.cjs deleted file mode 100644 index b23df5c5..00000000 --- a/tap-snapshots/test/snapshot/generateAlbumBanner.js.test.cjs +++ /dev/null @@ -1,18 +0,0 @@ -/* IMPORTANT - * This snapshot file is auto-generated, but designed for humans. - * It should be checked into source control and tracked carefully. - * Re-generate by setting TAP_SNAPSHOT=1 and running tests. - * Make sure to inspect the output below. Do not ignore changes! - */ -'use strict' -exports[`test/snapshot/generateAlbumBanner.js > TAP > generateAlbumBanner (snapshot) > basic behavior 1`] = ` -<div id="banner"><img src="media/album-art/cool-album/banner.png" width="800" height="200" alt="album banner"></div> -` - -exports[`test/snapshot/generateAlbumBanner.js > TAP > generateAlbumBanner (snapshot) > no banner 1`] = ` - -` - -exports[`test/snapshot/generateAlbumBanner.js > TAP > generateAlbumBanner (snapshot) > no dimensions 1`] = ` -<div id="banner"><img src="media/album-art/cool-album/banner.png" width="1100" height="200" alt="album banner"></div> -` diff --git a/tap-snapshots/test/snapshot/generateAlbumReleaseInfo.js.test.cjs b/tap-snapshots/test/snapshot/generateAlbumReleaseInfo.js.test.cjs deleted file mode 100644 index 14cce64e..00000000 --- a/tap-snapshots/test/snapshot/generateAlbumReleaseInfo.js.test.cjs +++ /dev/null @@ -1,41 +0,0 @@ -/* IMPORTANT - * This snapshot file is auto-generated, but designed for humans. - * It should be checked into source control and tracked carefully. - * Re-generate by setting TAP_SNAPSHOT=1 and running tests. - * Make sure to inspect the output below. Do not ignore changes! - */ -'use strict' -exports[`test/snapshot/generateAlbumReleaseInfo.js > TAP > generateAlbumReleaseInfo (snapshot) > basic behavior 1`] = ` -<p> - By <span class="contribution nowrap"><a href="artist/toby-fox/">Toby Fox</a> (music probably)</span> and <span class="contribution nowrap"><span class="text-with-tooltip"><span class="hoverable"><a class="text-with-tooltip-interaction-cue" href="artist/tensei/">Tensei</a></span><span class="tooltip contribution-tooltip"><span class="tooltip-content"><a class="external-link" href="https://tenseimusic.bandcamp.com/"> - <span class="external-icon"><svg><use href="static/misc/icons.svg#icon-bandcamp"></use></svg></span> - <span class="external-handle">tenseimusic</span> - </a> - <span class="external-platform">Bandcamp</span></span></span></span> (hot jams)</span>. - <br> - Cover art by <span class="contribution nowrap"><a href="artist/hb/">Hanni Brosh</a></span>. - <br> - Wallpaper art by <span class="contribution nowrap"><a href="artist/hb/">Hanni Brosh</a></span> and <span class="contribution nowrap"><a href="artist/niklink/">Niklink</a> (edits)</span>. - <br> - Banner art by <span class="contribution nowrap"><a href="artist/hb/">Hanni Brosh</a></span> and <span class="contribution nowrap"><a href="artist/niklink/">Niklink</a> (edits)</span>. - <br> - Released 3/14/2011. - <br> - Art released 4/1/1991. - <br> - Duration: ~10:25. -</p> -<p>Listen on <a class="external-link" href="https://homestuck.bandcamp.com/album/alterniabound-with-alternia">Bandcamp</a>, <a class="external-link" href="https://www.youtube.com/playlist?list=PLnVpmehyaOFZWO9QOZmD6A3TIK0wZ6xE2">YouTube (playlist)</a>, or <a class="external-link" href="https://www.youtube.com/watch?v=HO5V2uogkYc">YouTube (full album)</a>.</p> -` - -exports[`test/snapshot/generateAlbumReleaseInfo.js > TAP > generateAlbumReleaseInfo (snapshot) > equal cover art date 1`] = ` -<p>Released 4/12/2020.</p> -` - -exports[`test/snapshot/generateAlbumReleaseInfo.js > TAP > generateAlbumReleaseInfo (snapshot) > reduced details 1`] = ` - -` - -exports[`test/snapshot/generateAlbumReleaseInfo.js > TAP > generateAlbumReleaseInfo (snapshot) > URLs only 1`] = ` -<p>Listen on <a class="external-link" href="https://homestuck.bandcamp.com/foo">Bandcamp</a> or <a class="external-link" href="https://soundcloud.com/bar">SoundCloud</a>.</p> -` diff --git a/tap-snapshots/test/snapshot/generateAlbumSecondaryNav.js.test.cjs b/tap-snapshots/test/snapshot/generateAlbumSecondaryNav.js.test.cjs deleted file mode 100644 index 145dd0f7..00000000 --- a/tap-snapshots/test/snapshot/generateAlbumSecondaryNav.js.test.cjs +++ /dev/null @@ -1,34 +0,0 @@ -/* IMPORTANT - * This snapshot file is auto-generated, but designed for humans. - * It should be checked into source control and tracked carefully. - * Re-generate by setting TAP_SNAPSHOT=1 and running tests. - * Make sure to inspect the output below. Do not ignore changes! - */ -'use strict' -exports[`test/snapshot/generateAlbumSecondaryNav.js > TAP > generateAlbumSecondaryNav (snapshot) > basic behavior, mode: album 1`] = ` -<nav id="secondary-nav" class="album-secondary-nav with-previous-next"> - <span class="group-with-series"> - <span style="--primary-color: #abcdef"><a href="group/vcg/">VCG</a> (<span class="dot-switcher interpage"><span><a title="First" href="album/first/">Previous</a></span><span><a title="Last" href="album/last/">Next</a></span></span>)</span> - <span class="series-nav-link" style="--primary-color: #abcdef"><a class="series" href="group/vcg/">Series</a> (<span class="dot-switcher interpage"><span><a class="inert-previous-next-link">Previous</a></span><span><a title="Last" href="album/last/">Next</a></span></span>)</span> - </span> - <span style="--primary-color: #123456"><a href="group/bepis/">Bepis</a> (<span class="dot-switcher interpage"><span><a class="inert-previous-next-link">Previous</a></span><span><a title="Second" href="album/second/">Next</a></span></span>)</span> -</nav> -` - -exports[`test/snapshot/generateAlbumSecondaryNav.js > TAP > generateAlbumSecondaryNav (snapshot) > basic behavior, mode: track 1`] = ` -<nav id="secondary-nav" class="album-secondary-nav"> - <span class="dot-switcher"><span> - <span class="group-with-series"> - <span style="--primary-color: #abcdef"><a href="group/vcg/">VCG</a></span> - <span class="series-nav-link" style="--primary-color: #abcdef"><a class="series" href="group/vcg/">Series</a></span> - </span> - </span><span><span style="--primary-color: #123456"><a href="group/bepis/">Bepis</a></span></span></span> -</nav> -` - -exports[`test/snapshot/generateAlbumSecondaryNav.js > TAP > generateAlbumSecondaryNav (snapshot) > dateless album in mixed group 1`] = ` -<nav id="secondary-nav" class="album-secondary-nav with-previous-next"> - <span style="--primary-color: #abcdef"><a href="group/vcg/">VCG</a></span> - <span style="--primary-color: #123456"><a href="group/bepis/">Bepis</a></span> -</nav> -` diff --git a/tap-snapshots/test/snapshot/generateAlbumSidebarGroupBox.js.test.cjs b/tap-snapshots/test/snapshot/generateAlbumSidebarGroupBox.js.test.cjs deleted file mode 100644 index 6502f719..00000000 --- a/tap-snapshots/test/snapshot/generateAlbumSidebarGroupBox.js.test.cjs +++ /dev/null @@ -1,31 +0,0 @@ -/* IMPORTANT - * This snapshot file is auto-generated, but designed for humans. - * It should be checked into source control and tracked carefully. - * Re-generate by setting TAP_SNAPSHOT=1 and running tests. - * Make sure to inspect the output below. Do not ignore changes! - */ -'use strict' -exports[`test/snapshot/generateAlbumSidebarGroupBox.js > TAP > generateAlbumSidebarGroupBox (snapshot) > basic behavior, mode: album 1`] = ` -<div class="sidebar collapsible individual-group-sidebar-box"> - <h1><a href="group/vcg/">VCG</a></h1> - Very cool group. - <p>Visit on <a class="external-link" href="https://vcg.bandcamp.com/">Bandcamp</a> or <a class="external-link" href="https://youtube.com/@vcg">YouTube</a>.</p> - <p class="group-chronology-link">Next: <a href="album/last/">Last</a></p> - <p class="group-chronology-link">Previous: <a href="album/first/">First</a></p> -</div> -` - -exports[`test/snapshot/generateAlbumSidebarGroupBox.js > TAP > generateAlbumSidebarGroupBox (snapshot) > basic behavior, mode: track 1`] = ` -<div class="sidebar collapsible individual-group-sidebar-box"> - <h1><a href="group/vcg/">VCG</a></h1> - <p>Visit on <a class="external-link" href="https://vcg.bandcamp.com/">Bandcamp</a> or <a class="external-link" href="https://youtube.com/@vcg">YouTube</a>.</p> -</div> -` - -exports[`test/snapshot/generateAlbumSidebarGroupBox.js > TAP > generateAlbumSidebarGroupBox (snapshot) > dateless album in mixed group 1`] = ` -<div class="sidebar collapsible individual-group-sidebar-box"> - <h1><a href="group/vcg/">VCG</a></h1> - Very cool group. - <p>Visit on <a class="external-link" href="https://vcg.bandcamp.com/">Bandcamp</a> or <a class="external-link" href="https://youtube.com/@vcg">YouTube</a>.</p> -</div> -` diff --git a/tap-snapshots/test/snapshot/generateAlbumTrackList.js.test.cjs b/tap-snapshots/test/snapshot/generateAlbumTrackList.js.test.cjs deleted file mode 100644 index 40a8017d..00000000 --- a/tap-snapshots/test/snapshot/generateAlbumTrackList.js.test.cjs +++ /dev/null @@ -1,185 +0,0 @@ -/* IMPORTANT - * This snapshot file is auto-generated, but designed for humans. - * It should be checked into source control and tracked carefully. - * Re-generate by setting TAP_SNAPSHOT=1 and running tests. - * Make sure to inspect the output below. Do not ignore changes! - */ -'use strict' -exports[`test/snapshot/generateAlbumTrackList.js > TAP > generateAlbumTrackList (snapshot) > basic behavior, default track section 1`] = ` -<ul> - <li>(0:20) <a href="track/t1/">Track 1</a></li> - <li>[mocked: generateAlbumTrackListMissingDuration - slots: {}] <a href="track/t2/">Track 2</a></li> - <li>(0:40) <a href="track/t3/">Track 3</a></li> - <li style="--primary-color: #ea2e83">[mocked: generateAlbumTrackListMissingDuration - slots: {}] <a href="track/t4/">Track 4</a> <span class="by"><span class="chunkwrap">by <span class="contribution nowrap"><a href="artist/apricot/">Apricot</a></span>,</span> <span class="chunkwrap"><span class="contribution nowrap"><a href="artist/peach/">Peach</a></span>,</span> <span class="chunkwrap">and <span class="contribution nowrap"><a href="artist/cerise/">Cerise</a></span></span></span></li> -</ul> -` - -exports[`test/snapshot/generateAlbumTrackList.js > TAP > generateAlbumTrackList (snapshot) > basic behavior, with descriptions 1`] = ` -<dl class="album-group-list"> - <dt class="content-heading" tabindex="0"> - <span class="content-heading-main-title">First section: (~1:00)</span> - <template class="content-heading-sticky-title">First section:</template> - </dt> - <dd> - <blockquote><p>Why yes!</p></blockquote> - <ul> - <li>(0:20) <a href="track/t1/">Track 1</a></li> - <li>[mocked: generateAlbumTrackListMissingDuration - slots: {}] <a href="track/t2/">Track 2</a></li> - <li>(0:40) <a href="track/t3/">Track 3</a></li> - </ul> - </dd> - <dt class="content-heading" tabindex="0"> - <span class="content-heading-main-title">Second section:</span> - <template class="content-heading-sticky-title">Second section:</template> - </dt> - <dd> - <blockquote><p>How <em>contentful,</em> this is.</p></blockquote> - <ul><li style="--primary-color: #ea2e83">[mocked: generateAlbumTrackListMissingDuration - slots: {}] <a href="track/t4/">Track 4</a> <span class="by"><span class="chunkwrap">by <span class="contribution nowrap"><a href="artist/apricot/">Apricot</a></span>,</span> <span class="chunkwrap"><span class="contribution nowrap"><a href="artist/peach/">Peach</a></span>,</span> <span class="chunkwrap">and <span class="contribution nowrap"><a href="artist/cerise/">Cerise</a></span></span></span></li></ul> - </dd> -</dl> -` - -exports[`test/snapshot/generateAlbumTrackList.js > TAP > generateAlbumTrackList (snapshot) > basic behavior, with track sections 1`] = ` -<dl class="album-group-list"> - <dt class="content-heading" tabindex="0"> - <span class="content-heading-main-title">First section: (~1:00)</span> - <template class="content-heading-sticky-title">First section:</template> - </dt> - <dd> - <ul> - <li>(0:20) <a href="track/t1/">Track 1</a></li> - <li>[mocked: generateAlbumTrackListMissingDuration - slots: {}] <a href="track/t2/">Track 2</a></li> - <li>(0:40) <a href="track/t3/">Track 3</a></li> - </ul> - </dd> - <dt class="content-heading" tabindex="0"> - <span class="content-heading-main-title">Second section:</span> - <template class="content-heading-sticky-title">Second section:</template> - </dt> - <dd><ul><li style="--primary-color: #ea2e83">[mocked: generateAlbumTrackListMissingDuration - slots: {}] <a href="track/t4/">Track 4</a> <span class="by"><span class="chunkwrap">by <span class="contribution nowrap"><a href="artist/apricot/">Apricot</a></span>,</span> <span class="chunkwrap"><span class="contribution nowrap"><a href="artist/peach/">Peach</a></span>,</span> <span class="chunkwrap">and <span class="contribution nowrap"><a href="artist/cerise/">Cerise</a></span></span></span></li></ul></dd> -</dl> -` - -exports[`test/snapshot/generateAlbumTrackList.js > TAP > generateAlbumTrackList (snapshot) > collapseDurationScope: album 1`] = ` -<dl class="album-group-list"> - <dt class="content-heading" tabindex="0"> - <span class="content-heading-main-title">First section: (~1:00)</span> - <template class="content-heading-sticky-title">First section:</template> - </dt> - <dd> - <ul> - <li>(0:20) <a href="track/t1/">Track 1</a></li> - <li>[mocked: generateAlbumTrackListMissingDuration - slots: {}] <a href="track/t2/">Track 2</a></li> - <li>(0:40) <a href="track/t3/">Track 3</a></li> - </ul> - </dd> - <dt class="content-heading" tabindex="0"> - <span class="content-heading-main-title">Second section:</span> - <template class="content-heading-sticky-title">Second section:</template> - </dt> - <dd><ul><li style="--primary-color: #ea2e83">[mocked: generateAlbumTrackListMissingDuration - slots: {}] <a href="track/t4/">Track 4</a> <span class="by"><span class="chunkwrap">by <span class="contribution nowrap"><a href="artist/apricot/">Apricot</a></span>,</span> <span class="chunkwrap"><span class="contribution nowrap"><a href="artist/peach/">Peach</a></span>,</span> <span class="chunkwrap">and <span class="contribution nowrap"><a href="artist/cerise/">Cerise</a></span></span></span></li></ul></dd> -</dl> -<ul> - <li>(0:20) <a href="track/t1/">Track 1</a></li> - <li>[mocked: generateAlbumTrackListMissingDuration - slots: {}] <a href="track/t2/">Track 2</a></li> - <li>(0:40) <a href="track/t3/">Track 3</a></li> - <li style="--primary-color: #ea2e83">[mocked: generateAlbumTrackListMissingDuration - slots: {}] <a href="track/t4/">Track 4</a> <span class="by"><span class="chunkwrap">by <span class="contribution nowrap"><a href="artist/apricot/">Apricot</a></span>,</span> <span class="chunkwrap"><span class="contribution nowrap"><a href="artist/peach/">Peach</a></span>,</span> <span class="chunkwrap">and <span class="contribution nowrap"><a href="artist/cerise/">Cerise</a></span></span></span></li> -</ul> -<ul> - <li><a href="track/t2/">Track 2</a></li> - <li style="--primary-color: #ea2e83"><a href="track/t4/">Track 4</a> <span class="by"><span class="chunkwrap">by <span class="contribution nowrap"><a href="artist/apricot/">Apricot</a></span>,</span> <span class="chunkwrap"><span class="contribution nowrap"><a href="artist/peach/">Peach</a></span>,</span> <span class="chunkwrap">and <span class="contribution nowrap"><a href="artist/cerise/">Cerise</a></span></span></span></li> -</ul> -` - -exports[`test/snapshot/generateAlbumTrackList.js > TAP > generateAlbumTrackList (snapshot) > collapseDurationScope: never 1`] = ` -<dl class="album-group-list"> - <dt class="content-heading" tabindex="0"> - <span class="content-heading-main-title">First section: (~1:00)</span> - <template class="content-heading-sticky-title">First section:</template> - </dt> - <dd> - <ul> - <li>(0:20) <a href="track/t1/">Track 1</a></li> - <li>[mocked: generateAlbumTrackListMissingDuration - slots: {}] <a href="track/t2/">Track 2</a></li> - <li>(0:40) <a href="track/t3/">Track 3</a></li> - </ul> - </dd> - <dt class="content-heading" tabindex="0"> - <span class="content-heading-main-title">Second section:</span> - <template class="content-heading-sticky-title">Second section:</template> - </dt> - <dd><ul><li style="--primary-color: #ea2e83">[mocked: generateAlbumTrackListMissingDuration - slots: {}] <a href="track/t4/">Track 4</a> <span class="by"><span class="chunkwrap">by <span class="contribution nowrap"><a href="artist/apricot/">Apricot</a></span>,</span> <span class="chunkwrap"><span class="contribution nowrap"><a href="artist/peach/">Peach</a></span>,</span> <span class="chunkwrap">and <span class="contribution nowrap"><a href="artist/cerise/">Cerise</a></span></span></span></li></ul></dd> -</dl> -<ul> - <li>(0:20) <a href="track/t1/">Track 1</a></li> - <li>[mocked: generateAlbumTrackListMissingDuration - slots: {}] <a href="track/t2/">Track 2</a></li> - <li>(0:40) <a href="track/t3/">Track 3</a></li> - <li style="--primary-color: #ea2e83">[mocked: generateAlbumTrackListMissingDuration - slots: {}] <a href="track/t4/">Track 4</a> <span class="by"><span class="chunkwrap">by <span class="contribution nowrap"><a href="artist/apricot/">Apricot</a></span>,</span> <span class="chunkwrap"><span class="contribution nowrap"><a href="artist/peach/">Peach</a></span>,</span> <span class="chunkwrap">and <span class="contribution nowrap"><a href="artist/cerise/">Cerise</a></span></span></span></li> -</ul> -<ul> - <li>[mocked: generateAlbumTrackListMissingDuration - slots: {}] <a href="track/t2/">Track 2</a></li> - <li style="--primary-color: #ea2e83">[mocked: generateAlbumTrackListMissingDuration - slots: {}] <a href="track/t4/">Track 4</a> <span class="by"><span class="chunkwrap">by <span class="contribution nowrap"><a href="artist/apricot/">Apricot</a></span>,</span> <span class="chunkwrap"><span class="contribution nowrap"><a href="artist/peach/">Peach</a></span>,</span> <span class="chunkwrap">and <span class="contribution nowrap"><a href="artist/cerise/">Cerise</a></span></span></span></li> -</ul> -` - -exports[`test/snapshot/generateAlbumTrackList.js > TAP > generateAlbumTrackList (snapshot) > collapseDurationScope: section 1`] = ` -<dl class="album-group-list"> - <dt class="content-heading" tabindex="0"> - <span class="content-heading-main-title">First section: (~1:00)</span> - <template class="content-heading-sticky-title">First section:</template> - </dt> - <dd> - <ul> - <li>(0:20) <a href="track/t1/">Track 1</a></li> - <li>[mocked: generateAlbumTrackListMissingDuration - slots: {}] <a href="track/t2/">Track 2</a></li> - <li>(0:40) <a href="track/t3/">Track 3</a></li> - </ul> - </dd> - <dt class="content-heading" tabindex="0"> - <span class="content-heading-main-title">Second section:</span> - <template class="content-heading-sticky-title">Second section:</template> - </dt> - <dd><ul><li style="--primary-color: #ea2e83"><a href="track/t4/">Track 4</a> <span class="by"><span class="chunkwrap">by <span class="contribution nowrap"><a href="artist/apricot/">Apricot</a></span>,</span> <span class="chunkwrap"><span class="contribution nowrap"><a href="artist/peach/">Peach</a></span>,</span> <span class="chunkwrap">and <span class="contribution nowrap"><a href="artist/cerise/">Cerise</a></span></span></span></li></ul></dd> -</dl> -<ul> - <li>(0:20) <a href="track/t1/">Track 1</a></li> - <li>[mocked: generateAlbumTrackListMissingDuration - slots: {}] <a href="track/t2/">Track 2</a></li> - <li>(0:40) <a href="track/t3/">Track 3</a></li> - <li style="--primary-color: #ea2e83">[mocked: generateAlbumTrackListMissingDuration - slots: {}] <a href="track/t4/">Track 4</a> <span class="by"><span class="chunkwrap">by <span class="contribution nowrap"><a href="artist/apricot/">Apricot</a></span>,</span> <span class="chunkwrap"><span class="contribution nowrap"><a href="artist/peach/">Peach</a></span>,</span> <span class="chunkwrap">and <span class="contribution nowrap"><a href="artist/cerise/">Cerise</a></span></span></span></li> -</ul> -<ul> - <li><a href="track/t2/">Track 2</a></li> - <li style="--primary-color: #ea2e83"><a href="track/t4/">Track 4</a> <span class="by"><span class="chunkwrap">by <span class="contribution nowrap"><a href="artist/apricot/">Apricot</a></span>,</span> <span class="chunkwrap"><span class="contribution nowrap"><a href="artist/peach/">Peach</a></span>,</span> <span class="chunkwrap">and <span class="contribution nowrap"><a href="artist/cerise/">Cerise</a></span></span></span></li> -</ul> -` - -exports[`test/snapshot/generateAlbumTrackList.js > TAP > generateAlbumTrackList (snapshot) > collapseDurationScope: track 1`] = ` -<dl class="album-group-list"> - <dt class="content-heading" tabindex="0"> - <span class="content-heading-main-title">First section: (~1:00)</span> - <template class="content-heading-sticky-title">First section:</template> - </dt> - <dd> - <ul> - <li>(0:20) <a href="track/t1/">Track 1</a></li> - <li><a href="track/t2/">Track 2</a></li> - <li>(0:40) <a href="track/t3/">Track 3</a></li> - </ul> - </dd> - <dt class="content-heading" tabindex="0"> - <span class="content-heading-main-title">Second section:</span> - <template class="content-heading-sticky-title">Second section:</template> - </dt> - <dd><ul><li style="--primary-color: #ea2e83"><a href="track/t4/">Track 4</a> <span class="by"><span class="chunkwrap">by <span class="contribution nowrap"><a href="artist/apricot/">Apricot</a></span>,</span> <span class="chunkwrap"><span class="contribution nowrap"><a href="artist/peach/">Peach</a></span>,</span> <span class="chunkwrap">and <span class="contribution nowrap"><a href="artist/cerise/">Cerise</a></span></span></span></li></ul></dd> -</dl> -<ul> - <li>(0:20) <a href="track/t1/">Track 1</a></li> - <li><a href="track/t2/">Track 2</a></li> - <li>(0:40) <a href="track/t3/">Track 3</a></li> - <li style="--primary-color: #ea2e83"><a href="track/t4/">Track 4</a> <span class="by"><span class="chunkwrap">by <span class="contribution nowrap"><a href="artist/apricot/">Apricot</a></span>,</span> <span class="chunkwrap"><span class="contribution nowrap"><a href="artist/peach/">Peach</a></span>,</span> <span class="chunkwrap">and <span class="contribution nowrap"><a href="artist/cerise/">Cerise</a></span></span></span></li> -</ul> -<ul> - <li><a href="track/t2/">Track 2</a></li> - <li style="--primary-color: #ea2e83"><a href="track/t4/">Track 4</a> <span class="by"><span class="chunkwrap">by <span class="contribution nowrap"><a href="artist/apricot/">Apricot</a></span>,</span> <span class="chunkwrap"><span class="contribution nowrap"><a href="artist/peach/">Peach</a></span>,</span> <span class="chunkwrap">and <span class="contribution nowrap"><a href="artist/cerise/">Cerise</a></span></span></span></li> -</ul> -` diff --git a/tap-snapshots/test/snapshot/generateBanner.js.test.cjs b/tap-snapshots/test/snapshot/generateBanner.js.test.cjs deleted file mode 100644 index 870097c8..00000000 --- a/tap-snapshots/test/snapshot/generateBanner.js.test.cjs +++ /dev/null @@ -1,14 +0,0 @@ -/* IMPORTANT - * This snapshot file is auto-generated, but designed for humans. - * It should be checked into source control and tracked carefully. - * Re-generate by setting TAP_SNAPSHOT=1 and running tests. - * Make sure to inspect the output below. Do not ignore changes! - */ -'use strict' -exports[`test/snapshot/generateBanner.js > TAP > generateBanner (snapshot) > basic behavior 1`] = ` -<div id="banner"><img src="media/album-art/cool-album/banner.png" width="800" height="200" alt="Very cool banner art."></div> -` - -exports[`test/snapshot/generateBanner.js > TAP > generateBanner (snapshot) > no dimensions 1`] = ` -<div id="banner"><img src="media/album-art/cool-album/banner.png" width="1100" height="200"></div> -` diff --git a/tap-snapshots/test/snapshot/generateTrackReleaseInfo.js.test.cjs b/tap-snapshots/test/snapshot/generateTrackReleaseInfo.js.test.cjs deleted file mode 100644 index 098fe145..00000000 --- a/tap-snapshots/test/snapshot/generateTrackReleaseInfo.js.test.cjs +++ /dev/null @@ -1,36 +0,0 @@ -/* IMPORTANT - * This snapshot file is auto-generated, but designed for humans. - * It should be checked into source control and tracked carefully. - * Re-generate by setting TAP_SNAPSHOT=1 and running tests. - * Make sure to inspect the output below. Do not ignore changes! - */ -'use strict' -exports[`test/snapshot/generateTrackReleaseInfo.js > TAP > generateTrackReleaseInfo (snapshot) > basic behavior 1`] = ` -<p> - By <span class="contribution nowrap"><a href="artist/toby-fox/">Toby Fox</a></span>. - <br> - Released 11/29/2011. - <br> - Duration: 0:58. -</p> -<p>Listen on <a class="external-link" href="https://soundcloud.com/foo">SoundCloud</a> or <a class="external-link" href="https://youtube.com/watch?v=bar">YouTube</a>.</p> -` - -exports[`test/snapshot/generateTrackReleaseInfo.js > TAP > generateTrackReleaseInfo (snapshot) > cover artist contribs, non-unique 1`] = ` -<p>By <span class="contribution nowrap"><a href="artist/toby-fox/">Toby Fox</a></span>.</p> -<p>This wiki doesn't have any listening links for <i>Suspicious Track</i>.</p> -` - -exports[`test/snapshot/generateTrackReleaseInfo.js > TAP > generateTrackReleaseInfo (snapshot) > cover artist contribs, unique 1`] = ` -<p> - By <span class="contribution nowrap"><a href="artist/toby-fox/">Toby Fox</a></span>. - <br> - Cover art by <span class="contribution nowrap"><a href="artist/alpaca/">Alpaca</a> (🔥)</span>. -</p> -<p>This wiki doesn't have any listening links for <i>Suspicious Track</i>.</p> -` - -exports[`test/snapshot/generateTrackReleaseInfo.js > TAP > generateTrackReleaseInfo (snapshot) > reduced details 1`] = ` -<p>By <span class="contribution nowrap"><a href="artist/toby-fox/">Toby Fox</a></span>.</p> -<p>This wiki doesn't have any listening links for <i>Suspicious Track</i>.</p> -` diff --git a/tap-snapshots/test/snapshot/image.js.test.cjs b/tap-snapshots/test/snapshot/image.js.test.cjs deleted file mode 100644 index 283f4352..00000000 --- a/tap-snapshots/test/snapshot/image.js.test.cjs +++ /dev/null @@ -1,74 +0,0 @@ -/* IMPORTANT - * This snapshot file is auto-generated, but designed for humans. - * It should be checked into source control and tracked carefully. - * Re-generate by setting TAP_SNAPSHOT=1 and running tests. - * Make sure to inspect the output below. Do not ignore changes! - */ -'use strict' -exports[`test/snapshot/image.js > TAP > image (snapshot) > content warnings via tags 1`] = ` -<div class="image-container reveal"> - <div class="image-outer-area"> - <div class="image-inner-area"> - <img class="image" src="media/album-art/beyond-canon/cover.png"> - <span class="reveal-text-container"> - <span class="reveal-text"> - <img class="reveal-symbol" src="static/misc/warning.svg"> - <br> - <span class="reveal-warnings">too cool for school</span> - <br> - <span class="reveal-interaction">click to show</span> - </span> - </span> - </div> - </div> -</div> -` - -exports[`test/snapshot/image.js > TAP > image (snapshot) > dimensions 1`] = ` -<div class="image-container"><div class="image-outer-area"><div class="image-inner-area"><img class="image" width="600" height="400" src="foobar"></div></div></div> -` - -exports[`test/snapshot/image.js > TAP > image (snapshot) > dimensions with square 1`] = ` -<div class="image-container square"><div class="image-outer-area square-content"><div class="image-inner-area"><img class="image" width="600" height="400" src="foobar"></div></div></div> -` - -exports[`test/snapshot/image.js > TAP > image (snapshot) > lazy with square 1`] = ` -<noscript><div class="image-container square"><div class="image-outer-area square-content"><div class="image-inner-area"><img class="image" src="foobar"></div></div></div></noscript> -<div class="image-container square js-hide"><div class="image-outer-area square-content"><div class="image-inner-area"><img class="image lazy" data-original="foobar"></div></div></div> -` - -exports[`test/snapshot/image.js > TAP > image (snapshot) > link with file size 1`] = ` -<div class="image-container has-link"><div class="image-outer-area"><a class="image-link" href="media/album-art/pingas/cover.png"><div class="image-inner-area"><img class="image" src="media/album-art/pingas/cover.png"></div></a></div></div> -` - -exports[`test/snapshot/image.js > TAP > image (snapshot) > missing image path 1`] = ` -<div class="image-container placeholder-image"><div class="image-outer-area"><div class="image-inner-area"><div class="image-text-area">(This image file is missing)</div></div></div></div> -` - -exports[`test/snapshot/image.js > TAP > image (snapshot) > missing image path w/ missingSourceContent 1`] = ` -<div class="image-container placeholder-image"><div class="image-outer-area"><div class="image-inner-area"><div class="image-text-area">Cover's missing, whoops</div></div></div></div> -` - -exports[`test/snapshot/image.js > TAP > image (snapshot) > source missing 1`] = ` -<div class="image-container placeholder-image"><div class="image-outer-area"><div class="image-inner-area"><div class="image-text-area">Example of missing source message.</div></div></div></div> -` - -exports[`test/snapshot/image.js > TAP > image (snapshot) > source via path 1`] = ` -<div class="image-container"><div class="image-outer-area"><div class="image-inner-area"><img class="image" src="media/album-art/beyond-canon/cover.png"></div></div></div> -` - -exports[`test/snapshot/image.js > TAP > image (snapshot) > source via src 1`] = ` -<div class="image-container"><div class="image-outer-area"><div class="image-inner-area"><img class="image" src="https://example.com/bananas.gif"></div></div></div> -` - -exports[`test/snapshot/image.js > TAP > image (snapshot) > square 1`] = ` -<div class="image-container square"><div class="image-outer-area square-content"><div class="image-inner-area"><img class="image" src="foobar"></div></div></div> -` - -exports[`test/snapshot/image.js > TAP > image (snapshot) > thumb requested but source is gif 1`] = ` -<div class="image-container"><div class="image-outer-area"><div class="image-inner-area"><img class="image" src="media/flash-art/5426.gif"></div></div></div> -` - -exports[`test/snapshot/image.js > TAP > image (snapshot) > thumbnail details 1`] = ` -<div class="image-container"><div class="image-outer-area"><div class="image-inner-area"><img class="image" data-original-length="1200" data-thumbs="voluminous:1200 middling:900 petite:20" src="thumb/album-art/beyond-canon/cover.voluminous.jpg"></div></div></div> -` diff --git a/tap-snapshots/test/snapshot/linkArtist.js.test.cjs b/tap-snapshots/test/snapshot/linkArtist.js.test.cjs deleted file mode 100644 index ae3677c3..00000000 --- a/tap-snapshots/test/snapshot/linkArtist.js.test.cjs +++ /dev/null @@ -1,14 +0,0 @@ -/* IMPORTANT - * This snapshot file is auto-generated, but designed for humans. - * It should be checked into source control and tracked carefully. - * Re-generate by setting TAP_SNAPSHOT=1 and running tests. - * Make sure to inspect the output below. Do not ignore changes! - */ -'use strict' -exports[`test/snapshot/linkArtist.js > TAP > linkArtist (snapshot) > basic behavior 1`] = ` -<a href="artist/toby-fox/">Toby Fox</a> -` - -exports[`test/snapshot/linkArtist.js > TAP > linkArtist (snapshot) > prefer short name 1`] = ` -<span class="text-with-tooltip"><span class="hoverable"><a class="text-with-tooltip-interaction-cue" href="artist/55gore/">55gore</a></span><span class="tooltip thing-name-tooltip"><span class="tooltip-content">ICCTTCMDMIROTMCWMWFTPFTDDOTARHPOESWGBTWEATFCWSEBTSSFOFG</span></span></span> -` diff --git a/tap-snapshots/test/snapshot/linkContribution.js.test.cjs b/tap-snapshots/test/snapshot/linkContribution.js.test.cjs deleted file mode 100644 index 0c44a85c..00000000 --- a/tap-snapshots/test/snapshot/linkContribution.js.test.cjs +++ /dev/null @@ -1,118 +0,0 @@ -/* IMPORTANT - * This snapshot file is auto-generated, but designed for humans. - * It should be checked into source control and tracked carefully. - * Re-generate by setting TAP_SNAPSHOT=1 and running tests. - * Make sure to inspect the output below. Do not ignore changes! - */ -'use strict' -exports[`test/snapshot/linkContribution.js > TAP > linkContribution (snapshot) > loads of links 1`] = ` -<span class="contribution nowrap"><span class="text-with-tooltip"><span class="hoverable"><a class="text-with-tooltip-interaction-cue" href="artist/lorem-ipsum-lover/">Lorem Ipsum Lover</a></span><span class="tooltip contribution-tooltip"><span class="tooltip-content"><a class="external-link" href="https://loremipsum.io"> - <span class="external-icon"><svg><use href="static/misc/icons.svg#icon-globe"></use></svg></span> - <span class="external-handle">loremipsum.io</span> - </a> - <span class="external-platform">Other</span> - <a class="external-link" href="https://loremipsum.io/generator/"> - <span class="external-icon"><svg><use href="static/misc/icons.svg#icon-globe"></use></svg></span> - <span class="external-handle">loremipsum.io</span> - </a> - <span class="external-platform">Other</span> - <a class="external-link" href="https://loremipsum.io/#meaning"> - <span class="external-icon"><svg><use href="static/misc/icons.svg#icon-globe"></use></svg></span> - <span class="external-handle">loremipsum.io</span> - </a> - <span class="external-platform">Other</span> - <a class="external-link" href="https://loremipsum.io/#usage-and-examples"> - <span class="external-icon"><svg><use href="static/misc/icons.svg#icon-globe"></use></svg></span> - <span class="external-handle">loremipsum.io</span> - </a> - <span class="external-platform">Other</span> - <a class="external-link" href="https://loremipsum.io/#controversy"> - <span class="external-icon"><svg><use href="static/misc/icons.svg#icon-globe"></use></svg></span> - <span class="external-handle">loremipsum.io</span> - </a> - <span class="external-platform">Other</span> - <a class="external-link" href="https://loremipsum.io/#when-to-use-lorem-ipsum"> - <span class="external-icon"><svg><use href="static/misc/icons.svg#icon-globe"></use></svg></span> - <span class="external-handle">loremipsum.io</span> - </a> - <span class="external-platform">Other</span> - <a class="external-link" href="https://loremipsum.io/#lorem-ipsum-all-the-things"> - <span class="external-icon"><svg><use href="static/misc/icons.svg#icon-globe"></use></svg></span> - <span class="external-handle">loremipsum.io</span> - </a> - <span class="external-platform">Other</span> - <a class="external-link" href="https://loremipsum.io/#original-source"> - <span class="external-icon"><svg><use href="static/misc/icons.svg#icon-globe"></use></svg></span> - <span class="external-handle">loremipsum.io</span> - </a> - <span class="external-platform">Other</span></span></span></span></span> -` - -exports[`test/snapshot/linkContribution.js > TAP > linkContribution (snapshot) > no accents 1`] = ` -<span class="contribution nowrap"><a href="artist/clark-powell/">Clark Powell</a></span> -<span class="contribution nowrap"><a href="artist/the-big-baddies/">Grounder & Scratch</a></span> -<span class="contribution nowrap"><a href="artist/toby-fox/">Toby Fox</a></span> -` - -exports[`test/snapshot/linkContribution.js > TAP > linkContribution (snapshot) > no preventWrapping 1`] = ` -<span class="contribution"><span class="text-with-tooltip"><span class="hoverable"><a class="text-with-tooltip-interaction-cue" href="artist/clark-powell/">Clark Powell</a></span><span class="tooltip contribution-tooltip"><span class="tooltip-content"><a class="external-link" href="https://soundcloud.com/plazmataz"> - <span class="external-icon"><svg><use href="static/misc/icons.svg#icon-soundcloud"></use></svg></span> - <span class="external-handle">plazmataz</span> - </a> - <span class="external-platform">SoundCloud</span></span></span></span></span> -<span class="contribution"><a href="artist/the-big-baddies/">Grounder & Scratch</a> (Snooping)</span> -<span class="contribution"><span class="text-with-tooltip"><span class="hoverable"><a class="text-with-tooltip-interaction-cue" href="artist/toby-fox/">Toby Fox</a></span><span class="tooltip contribution-tooltip"><span class="tooltip-content"><a class="external-link" href="https://tobyfox.bandcamp.com/"> - <span class="external-icon"><svg><use href="static/misc/icons.svg#icon-bandcamp"></use></svg></span> - <span class="external-handle">tobyfox</span> - </a> - <span class="external-platform">Bandcamp</span> - <a class="external-link" href="https://toby.fox/"> - <span class="external-icon"><svg><use href="static/misc/icons.svg#icon-globe"></use></svg></span> - <span class="external-handle">toby.fox</span> - </a> - <span class="external-platform">Other</span></span></span></span> (Arrangement)</span> -` - -exports[`test/snapshot/linkContribution.js > TAP > linkContribution (snapshot) > only showAnnotation 1`] = ` -<span class="contribution nowrap"><a href="artist/clark-powell/">Clark Powell</a></span> -<span class="contribution nowrap"><a href="artist/the-big-baddies/">Grounder & Scratch</a> (Snooping)</span> -<span class="contribution nowrap"><a href="artist/toby-fox/">Toby Fox</a> (Arrangement)</span> -` - -exports[`test/snapshot/linkContribution.js > TAP > linkContribution (snapshot) > only showExternalLinks 1`] = ` -<span class="contribution nowrap"><span class="text-with-tooltip"><span class="hoverable"><a class="text-with-tooltip-interaction-cue" href="artist/clark-powell/">Clark Powell</a></span><span class="tooltip contribution-tooltip"><span class="tooltip-content"><a class="external-link" href="https://soundcloud.com/plazmataz"> - <span class="external-icon"><svg><use href="static/misc/icons.svg#icon-soundcloud"></use></svg></span> - <span class="external-handle">plazmataz</span> - </a> - <span class="external-platform">SoundCloud</span></span></span></span></span> -<span class="contribution nowrap"><a href="artist/the-big-baddies/">Grounder & Scratch</a></span> -<span class="contribution nowrap"><span class="text-with-tooltip"><span class="hoverable"><a class="text-with-tooltip-interaction-cue" href="artist/toby-fox/">Toby Fox</a></span><span class="tooltip contribution-tooltip"><span class="tooltip-content"><a class="external-link" href="https://tobyfox.bandcamp.com/"> - <span class="external-icon"><svg><use href="static/misc/icons.svg#icon-bandcamp"></use></svg></span> - <span class="external-handle">tobyfox</span> - </a> - <span class="external-platform">Bandcamp</span> - <a class="external-link" href="https://toby.fox/"> - <span class="external-icon"><svg><use href="static/misc/icons.svg#icon-globe"></use></svg></span> - <span class="external-handle">toby.fox</span> - </a> - <span class="external-platform">Other</span></span></span></span></span> -` - -exports[`test/snapshot/linkContribution.js > TAP > linkContribution (snapshot) > showAnnotation & showExternalLinks 1`] = ` -<span class="contribution nowrap"><span class="text-with-tooltip"><span class="hoverable"><a class="text-with-tooltip-interaction-cue" href="artist/clark-powell/">Clark Powell</a></span><span class="tooltip contribution-tooltip"><span class="tooltip-content"><a class="external-link" href="https://soundcloud.com/plazmataz"> - <span class="external-icon"><svg><use href="static/misc/icons.svg#icon-soundcloud"></use></svg></span> - <span class="external-handle">plazmataz</span> - </a> - <span class="external-platform">SoundCloud</span></span></span></span></span> -<span class="contribution nowrap"><a href="artist/the-big-baddies/">Grounder & Scratch</a> (Snooping)</span> -<span class="contribution nowrap"><span class="text-with-tooltip"><span class="hoverable"><a class="text-with-tooltip-interaction-cue" href="artist/toby-fox/">Toby Fox</a></span><span class="tooltip contribution-tooltip"><span class="tooltip-content"><a class="external-link" href="https://tobyfox.bandcamp.com/"> - <span class="external-icon"><svg><use href="static/misc/icons.svg#icon-bandcamp"></use></svg></span> - <span class="external-handle">tobyfox</span> - </a> - <span class="external-platform">Bandcamp</span> - <a class="external-link" href="https://toby.fox/"> - <span class="external-icon"><svg><use href="static/misc/icons.svg#icon-globe"></use></svg></span> - <span class="external-handle">toby.fox</span> - </a> - <span class="external-platform">Other</span></span></span></span> (Arrangement)</span> -` diff --git a/tap-snapshots/test/snapshot/linkExternal.js.test.cjs b/tap-snapshots/test/snapshot/linkExternal.js.test.cjs deleted file mode 100644 index 03192e82..00000000 --- a/tap-snapshots/test/snapshot/linkExternal.js.test.cjs +++ /dev/null @@ -1,228 +0,0 @@ -/* IMPORTANT - * This snapshot file is auto-generated, but designed for humans. - * It should be checked into source control and tracked carefully. - * Re-generate by setting TAP_SNAPSHOT=1 and running tests. - * Make sure to inspect the output below. Do not ignore changes! - */ -'use strict' -exports[`test/snapshot/linkExternal.js > TAP > linkExternal (snapshot) > context: album, style: handle 1`] = ` -<a class="external-link" href="https://youtu.be/abc">YouTube</a> -<a class="external-link" href="https://youtube.com/watch?v=abc">YouTube</a> -<a class="external-link" href="https://youtube.com/Playlist?list=kweh">YouTube</a> -` - -exports[`test/snapshot/linkExternal.js > TAP > linkExternal (snapshot) > context: album, style: platform 1`] = ` -<a class="external-link" href="https://youtu.be/abc">YouTube</a> -<a class="external-link" href="https://youtube.com/watch?v=abc">YouTube</a> -<a class="external-link" href="https://youtube.com/Playlist?list=kweh">YouTube</a> -` - -exports[`test/snapshot/linkExternal.js > TAP > linkExternal (snapshot) > context: albumMultipleTracks, style: handle 1`] = ` -<a class="external-link" href="https://youtu.be/abc">YouTube (full album)</a> -<a class="external-link" href="https://youtube.com/watch?v=abc">YouTube (full album)</a> -<a class="external-link" href="https://youtube.com/Playlist?list=kweh">YouTube</a> -` - -exports[`test/snapshot/linkExternal.js > TAP > linkExternal (snapshot) > context: albumMultipleTracks, style: platform 1`] = ` -<a class="external-link" href="https://youtu.be/abc">YouTube (full album)</a> -<a class="external-link" href="https://youtube.com/watch?v=abc">YouTube (full album)</a> -<a class="external-link" href="https://youtube.com/Playlist?list=kweh">YouTube</a> -` - -exports[`test/snapshot/linkExternal.js > TAP > linkExternal (snapshot) > context: albumNoTracks, style: handle 1`] = ` -<a class="external-link" href="https://youtu.be/abc">YouTube</a> -<a class="external-link" href="https://youtube.com/watch?v=abc">YouTube</a> -<a class="external-link" href="https://youtube.com/Playlist?list=kweh">YouTube</a> -` - -exports[`test/snapshot/linkExternal.js > TAP > linkExternal (snapshot) > context: albumNoTracks, style: platform 1`] = ` -<a class="external-link" href="https://youtu.be/abc">YouTube</a> -<a class="external-link" href="https://youtube.com/watch?v=abc">YouTube</a> -<a class="external-link" href="https://youtube.com/Playlist?list=kweh">YouTube</a> -` - -exports[`test/snapshot/linkExternal.js > TAP > linkExternal (snapshot) > context: albumOneTrack, style: handle 1`] = ` -<a class="external-link" href="https://youtu.be/abc">YouTube</a> -<a class="external-link" href="https://youtube.com/watch?v=abc">YouTube</a> -<a class="external-link" href="https://youtube.com/Playlist?list=kweh">YouTube</a> -` - -exports[`test/snapshot/linkExternal.js > TAP > linkExternal (snapshot) > context: albumOneTrack, style: platform 1`] = ` -<a class="external-link" href="https://youtu.be/abc">YouTube</a> -<a class="external-link" href="https://youtube.com/watch?v=abc">YouTube</a> -<a class="external-link" href="https://youtube.com/Playlist?list=kweh">YouTube</a> -` - -exports[`test/snapshot/linkExternal.js > TAP > linkExternal (snapshot) > context: flash, style: handle 1`] = ` -<a class="external-link" href="https://www.bgreco.net/hsflash/002238.html">bgreco.net (high quality audio)</a> -<a class="external-link" href="https://homestuck.com/story/1234">Homestuck (page 1234)</a> -<a class="external-link" href="https://homestuck.com/story/pony">Homestuck (secret page)</a> -<a class="external-link" href="https://www.youtube.com/watch?v=wKgOp3Kg2wI">YouTube (on any device)</a> -<a class="external-link" href="https://youtu.be/IOcvkkklWmY">YouTube (on any device)</a> -<a class="external-link" href="https://some.external.site/foo/bar/">some.external.site</a> -` - -exports[`test/snapshot/linkExternal.js > TAP > linkExternal (snapshot) > context: flash, style: platform 1`] = ` -<a class="external-link" href="https://www.bgreco.net/hsflash/002238.html">bgreco.net (high quality audio)</a> -<a class="external-link" href="https://homestuck.com/story/1234">Homestuck (page 1234)</a> -<a class="external-link" href="https://homestuck.com/story/pony">Homestuck (secret page)</a> -<a class="external-link" href="https://www.youtube.com/watch?v=wKgOp3Kg2wI">YouTube (on any device)</a> -<a class="external-link" href="https://youtu.be/IOcvkkklWmY">YouTube (on any device)</a> -<a class="external-link" href="https://some.external.site/foo/bar/">some.external.site</a> -` - -exports[`test/snapshot/linkExternal.js > TAP > linkExternal (snapshot) > context: generic, style: handle 1`] = ` -<a class="external-link" href="https://music.apple.com/us/artist/system-of-a-down/462715">Apple Music</a> -<a class="external-link" href="https://www.artstation.com/eevaningtea">eevaningtea</a> -<a class="external-link" href="https://witnesstheabsurd.artstation.com/">witnesstheabsurd</a> -<a class="external-link" href="https://music.solatrus.com/">music.solatrus.com</a> -<a class="external-link" href="https://homestuck.bandcamp.com/">homestuck</a> -<a class="external-link" href="https://bsky.app/profile/jacobtheloofah.bsky.social">jacobtheloofah</a> -<a class="external-link" href="https://aliceflare.carrd.co">aliceflare</a> -<a class="external-link" href="https://bigchaslappa.carrd.co/">bigchaslappa</a> -<a class="external-link" href="https://cohost.org/cosmoptera">cosmoptera</a> -<a class="external-link" href="https://music.deconreconstruction.com/albums/catch-322">MUSIC@DCRC</a> -<a class="external-link" href="https://music.deconreconstruction.com/albums/catch-322?track=arcjecs-theme">MUSIC@DCRC</a> -<a class="external-link" href="https://www.deconreconstruction.com/">Deconreconstruction</a> -<a class="external-link" href="https://culdhira.deviantart.com">culdhira</a> -<a class="external-link" href="https://www.deviantart.com/chesswanderlust-sama">chesswanderlust-sama</a> -<a class="external-link" href="https://www.deviantart.com/shilloshilloh/art/Homestuck-Jake-English-268874606">DeviantArt</a> -<a class="external-link" href="https://www.facebook.com/DoomedCloud/">DoomedCloud</a> -<a class="external-link" href="https://www.facebook.com/pages/WoodenToaster/280642235307371">WoodenToaster</a> -<a class="external-link" href="https://www.facebook.com/Svixy/posts/400018786702633">Facebook</a> -<a class="external-link" href="https://mspaintadventures.fandom.com/wiki/Draconian_Dignitary">MSPA Wiki (Draconian Dignitary)</a> -<a class="external-link" href="https://mspaintadventures.fandom.com/wiki/">MSPA Wiki</a> -<a class="external-link" href="https://mspaintadventures.fandom.com/">MSPA Wiki</a> -<a class="external-link" href="https://community.fandom.com/">Fandom</a> -<a class="external-link" href="https://community.fandom.com/wiki/">Fandom</a> -<a class="external-link" href="https://community.fandom.com/wiki/Community_Central">Fandom</a> -<a class="external-link" href="https://gamebanana.com/members/2028092">GameBanana</a> -<a class="external-link" href="https://gamebanana.com/mods/459476">GameBanana</a> -<a class="external-link" href="https://homestuck.com/">Homestuck</a> -<a class="external-link" href="https://hsmusic.wiki/media/misc/archive/Firefly%20Cloud%20Remix.mp3">HSMusic (wiki archive)</a> -<a class="external-link" href="https://hsmusic.wiki/feedback/">HSMusic</a> -<a class="external-link" href="https://archive.org/details/a-life-well-lived">Internet Archive</a> -<a class="external-link" href="https://archive.org/details/VastError_Volume1/11+Renaissance.mp3">Internet Archive</a> -<a class="external-link" href="https://instagram.com/bass.and.noises">bass.and.noises</a> -<a class="external-link" href="https://www.instagram.com/levc_egm/">levc_egm</a> -<a class="external-link" href="https://tuyoki.itch.io/">tuyoki</a> -<a class="external-link" href="https://itch.io/profile/bravelittletoreador">bravelittletoreador</a> -<a class="external-link" href="https://ko-fi.com/gnaach">gnaach</a> -<a class="external-link" href="https://linktr.ee/bbpanzu">bbpanzu</a> -<a class="external-link" href="https://types.pl/">types.pl</a> -<a class="external-link" href="https://canwc.mspfa.com/">MSPFA</a> -<a class="external-link" href="https://mspfa.com/?s=12003&p=1045">MSPFA</a> -<a class="external-link" href="https://mspfa.com/user/?u=103334508819793669241">MSPFA</a> -<a class="external-link" href="https://wodaro.neocities.org">wodaro.neocities.org</a> -<a class="external-link" href="https://neomints.neocities.org/">neomints.neocities.org</a> -<a class="external-link" href="https://buzinkai.newgrounds.com/">buzinkai</a> -<a class="external-link" href="https://www.newgrounds.com/audio/listen/1256058">Newgrounds</a> -<a class="external-link" href="https://www.patreon.com/CecilyRenns">CecilyRenns</a> -<a class="external-link" href="https://www.poetryfoundation.org/poets/christina-rossetti">Poetry Foundation</a> -<a class="external-link" href="https://www.poetryfoundation.org/poems/45000/remember-56d224509b7ae">Poetry Foundation</a> -<a class="external-link" href="https://soundcloud.com/plazmataz">plazmataz</a> -<a class="external-link" href="https://soundcloud.com/worthikids/1-i-accidentally-broke-my">SoundCloud</a> -<a class="external-link" href="https://open.spotify.com/artist/63SNNpNOicDzG3LY82G4q3">Spotify</a> -<a class="external-link" href="https://open.spotify.com/album/0iHvPD8rM3hQa0qeVtPQ3t">Spotify</a> -<a class="external-link" href="https://open.spotify.com/track/6YEGQH32aAXb9vQQbBrPlw">Spotify</a> -<a class="external-link" href="https://www.tiktok.com/@richaadeb">richaadeb</a> -<a class="external-link" href="https://toyhou.se/ghastaboo">ghastaboo</a> -<a class="external-link" href="https://aeritus.tumblr.com/">aeritus</a> -<a class="external-link" href="https://vol5anthology.tumblr.com/post/159528808107/hey-everyone-its-413-and-that-means-we-have">vol5anthology</a> -<a class="external-link" href="https://www.tumblr.com/electricwestern">electricwestern</a> -<a class="external-link" href="https://www.tumblr.com/spellmynamewithabang/142767566733/happy-413-this-is-the-first-time-anyones-heard">Tumblr</a> -<a class="external-link" href="https://www.twitch.tv/ajhebard">ajhebard</a> -<a class="external-link" href="https://www.twitch.tv/vargskelethor/">vargskelethor/</a> -<a class="external-link" href="https://twitter.com/awkwarddoesart">awkwarddoesart</a> -<a class="external-link" href="https://twitter.com/purenonsens/">purenonsens</a> -<a class="external-link" href="https://twitter.com/circlejourney/status/1202265927183548416">Twitter</a> -<a class="external-link" href="https://web.archive.org/web/20120405160556/https://homestuck.bandcamp.com/album/colours-and-mayhem-universe-a">Wayback Machine</a> -<a class="external-link" href="https://web.archive.org/web/20160807111207/http://griffinspacejam.com:80/">Wayback Machine</a> -<a class="external-link" href="https://en.wikipedia.org/wiki/Haydn_Quartet_(vocal_ensemble)">Wikipedia</a> -<a class="external-link" href="https://youtube.com/@bani-chan8949">bani-chan8949</a> -<a class="external-link" href="https://www.youtube.com/@Razzie16">Razzie16</a> -<a class="external-link" href="https://www.youtube.com/channel/UCQXfvlKkpbOqEz4BepHqK7g">YouTube</a> -<a class="external-link" href="https://www.youtube.com/watch?v=6ekVnZm29kw">YouTube</a> -<a class="external-link" href="https://youtu.be/WBkC038wSio">YouTube</a> -<a class="external-link" href="https://www.youtube.com/playlist?list=PLy5UGIMKOXpONMExgI7lVYFwQa54QFp_H">YouTube</a> -` - -exports[`test/snapshot/linkExternal.js > TAP > linkExternal (snapshot) > context: generic, style: platform 1`] = ` -<a class="external-link" href="https://music.apple.com/us/artist/system-of-a-down/462715">Apple Music</a> -<a class="external-link" href="https://www.artstation.com/eevaningtea">ArtStation</a> -<a class="external-link" href="https://witnesstheabsurd.artstation.com/">ArtStation</a> -<a class="external-link" href="https://music.solatrus.com/">Bandcamp (music.solatrus.com)</a> -<a class="external-link" href="https://homestuck.bandcamp.com/">Bandcamp</a> -<a class="external-link" href="https://bsky.app/profile/jacobtheloofah.bsky.social">Bluesky</a> -<a class="external-link" href="https://aliceflare.carrd.co">Carrd</a> -<a class="external-link" href="https://bigchaslappa.carrd.co/">Carrd</a> -<a class="external-link" href="https://cohost.org/cosmoptera">Cohost</a> -<a class="external-link" href="https://music.deconreconstruction.com/albums/catch-322">MUSIC@DCRC</a> -<a class="external-link" href="https://music.deconreconstruction.com/albums/catch-322?track=arcjecs-theme">MUSIC@DCRC</a> -<a class="external-link" href="https://www.deconreconstruction.com/">Deconreconstruction</a> -<a class="external-link" href="https://culdhira.deviantart.com">DeviantArt</a> -<a class="external-link" href="https://www.deviantart.com/chesswanderlust-sama">DeviantArt</a> -<a class="external-link" href="https://www.deviantart.com/shilloshilloh/art/Homestuck-Jake-English-268874606">DeviantArt</a> -<a class="external-link" href="https://www.facebook.com/DoomedCloud/">Facebook</a> -<a class="external-link" href="https://www.facebook.com/pages/WoodenToaster/280642235307371">Facebook</a> -<a class="external-link" href="https://www.facebook.com/Svixy/posts/400018786702633">Facebook</a> -<a class="external-link" href="https://mspaintadventures.fandom.com/wiki/Draconian_Dignitary">MSPA Wiki (Draconian Dignitary)</a> -<a class="external-link" href="https://mspaintadventures.fandom.com/wiki/">MSPA Wiki</a> -<a class="external-link" href="https://mspaintadventures.fandom.com/">MSPA Wiki</a> -<a class="external-link" href="https://community.fandom.com/">Fandom</a> -<a class="external-link" href="https://community.fandom.com/wiki/">Fandom</a> -<a class="external-link" href="https://community.fandom.com/wiki/Community_Central">Fandom</a> -<a class="external-link" href="https://gamebanana.com/members/2028092">GameBanana</a> -<a class="external-link" href="https://gamebanana.com/mods/459476">GameBanana</a> -<a class="external-link" href="https://homestuck.com/">Homestuck</a> -<a class="external-link" href="https://hsmusic.wiki/media/misc/archive/Firefly%20Cloud%20Remix.mp3">HSMusic (wiki archive)</a> -<a class="external-link" href="https://hsmusic.wiki/feedback/">HSMusic</a> -<a class="external-link" href="https://archive.org/details/a-life-well-lived">Internet Archive</a> -<a class="external-link" href="https://archive.org/details/VastError_Volume1/11+Renaissance.mp3">Internet Archive</a> -<a class="external-link" href="https://instagram.com/bass.and.noises">Instagram</a> -<a class="external-link" href="https://www.instagram.com/levc_egm/">Instagram</a> -<a class="external-link" href="https://tuyoki.itch.io/">itch.io</a> -<a class="external-link" href="https://itch.io/profile/bravelittletoreador">itch.io</a> -<a class="external-link" href="https://ko-fi.com/gnaach">Ko-fi</a> -<a class="external-link" href="https://linktr.ee/bbpanzu">Linktree</a> -<a class="external-link" href="https://types.pl/">Mastodon (types.pl)</a> -<a class="external-link" href="https://canwc.mspfa.com/">MSPFA</a> -<a class="external-link" href="https://mspfa.com/?s=12003&p=1045">MSPFA</a> -<a class="external-link" href="https://mspfa.com/user/?u=103334508819793669241">MSPFA</a> -<a class="external-link" href="https://wodaro.neocities.org">Neocities</a> -<a class="external-link" href="https://neomints.neocities.org/">Neocities</a> -<a class="external-link" href="https://buzinkai.newgrounds.com/">Newgrounds</a> -<a class="external-link" href="https://www.newgrounds.com/audio/listen/1256058">Newgrounds</a> -<a class="external-link" href="https://www.patreon.com/CecilyRenns">Patreon</a> -<a class="external-link" href="https://www.poetryfoundation.org/poets/christina-rossetti">Poetry Foundation</a> -<a class="external-link" href="https://www.poetryfoundation.org/poems/45000/remember-56d224509b7ae">Poetry Foundation</a> -<a class="external-link" href="https://soundcloud.com/plazmataz">SoundCloud</a> -<a class="external-link" href="https://soundcloud.com/worthikids/1-i-accidentally-broke-my">SoundCloud</a> -<a class="external-link" href="https://open.spotify.com/artist/63SNNpNOicDzG3LY82G4q3">Spotify</a> -<a class="external-link" href="https://open.spotify.com/album/0iHvPD8rM3hQa0qeVtPQ3t">Spotify</a> -<a class="external-link" href="https://open.spotify.com/track/6YEGQH32aAXb9vQQbBrPlw">Spotify</a> -<a class="external-link" href="https://www.tiktok.com/@richaadeb">TikTok</a> -<a class="external-link" href="https://toyhou.se/ghastaboo">Toyhouse</a> -<a class="external-link" href="https://aeritus.tumblr.com/">Tumblr</a> -<a class="external-link" href="https://vol5anthology.tumblr.com/post/159528808107/hey-everyone-its-413-and-that-means-we-have">Tumblr</a> -<a class="external-link" href="https://www.tumblr.com/electricwestern">Tumblr</a> -<a class="external-link" href="https://www.tumblr.com/spellmynamewithabang/142767566733/happy-413-this-is-the-first-time-anyones-heard">Tumblr</a> -<a class="external-link" href="https://www.twitch.tv/ajhebard">Twitch</a> -<a class="external-link" href="https://www.twitch.tv/vargskelethor/">Twitch</a> -<a class="external-link" href="https://twitter.com/awkwarddoesart">Twitter</a> -<a class="external-link" href="https://twitter.com/purenonsens/">Twitter</a> -<a class="external-link" href="https://twitter.com/circlejourney/status/1202265927183548416">Twitter</a> -<a class="external-link" href="https://web.archive.org/web/20120405160556/https://homestuck.bandcamp.com/album/colours-and-mayhem-universe-a">Wayback Machine</a> -<a class="external-link" href="https://web.archive.org/web/20160807111207/http://griffinspacejam.com:80/">Wayback Machine</a> -<a class="external-link" href="https://en.wikipedia.org/wiki/Haydn_Quartet_(vocal_ensemble)">Wikipedia</a> -<a class="external-link" href="https://youtube.com/@bani-chan8949">YouTube</a> -<a class="external-link" href="https://www.youtube.com/@Razzie16">YouTube</a> -<a class="external-link" href="https://www.youtube.com/channel/UCQXfvlKkpbOqEz4BepHqK7g">YouTube</a> -<a class="external-link" href="https://www.youtube.com/watch?v=6ekVnZm29kw">YouTube</a> -<a class="external-link" href="https://youtu.be/WBkC038wSio">YouTube</a> -<a class="external-link" href="https://www.youtube.com/playlist?list=PLy5UGIMKOXpONMExgI7lVYFwQa54QFp_H">YouTube</a> -` - -exports[`test/snapshot/linkExternal.js > TAP > linkExternal (snapshot) > unknown domain (arbitrary world wide web path) 1`] = ` -<a class="external-link" href="https://snoo.ping.as/usual/i/see/">snoo.ping.as</a> -` diff --git a/tap-snapshots/test/snapshot/linkTemplate.js.test.cjs b/tap-snapshots/test/snapshot/linkTemplate.js.test.cjs deleted file mode 100644 index b5acde91..00000000 --- a/tap-snapshots/test/snapshot/linkTemplate.js.test.cjs +++ /dev/null @@ -1,32 +0,0 @@ -/* IMPORTANT - * This snapshot file is auto-generated, but designed for humans. - * It should be checked into source control and tracked carefully. - * Re-generate by setting TAP_SNAPSHOT=1 and running tests. - * Make sure to inspect the output below. Do not ignore changes! - */ -'use strict' -exports[`test/snapshot/linkTemplate.js > TAP > linkTemplate (snapshot) > fill many slots 1`] = ` -<a class="dog" id="cat1" href="https://hsmusic.wiki/media/cool%20file.pdf#fooey">My Cool Link</a> -` - -exports[`test/snapshot/linkTemplate.js > TAP > linkTemplate (snapshot) > fill path slot & provide appendIndexHTML 1`] = ` -<a href="/c*lzone/myCoolPath/ham/pineapple/tomato/index.html">delish</a> -` - -exports[`test/snapshot/linkTemplate.js > TAP > linkTemplate (snapshot) > link in content 1`] = ` -<a href="#the-more-ye-know"> - Oh geez oh heck - There's a link in here!! - But here's <b>a normal tag.</b> - <div>Gotta keep them normal tags.</div> - <div>But not... NESTED LINKS, OOO.</div> -</a> -` - -exports[`test/snapshot/linkTemplate.js > TAP > linkTemplate (snapshot) > missing content 1`] = ` -<a href="banana">(Missing link content)</a> -` - -exports[`test/snapshot/linkTemplate.js > TAP > linkTemplate (snapshot) > special characters in path argument 1`] = ` -<a href="media/album-additional/homestuck-vol-1/Showtime%20(Piano%20Refrain)%20-%20%23xXxAwesomeSheetMusick%3FrxXx%23.pdf">Damn, that's some good sheet music</a> -` diff --git a/tap-snapshots/test/snapshot/linkThing.js.test.cjs b/tap-snapshots/test/snapshot/linkThing.js.test.cjs deleted file mode 100644 index 7dd8b003..00000000 --- a/tap-snapshots/test/snapshot/linkThing.js.test.cjs +++ /dev/null @@ -1,45 +0,0 @@ -/* IMPORTANT - * This snapshot file is auto-generated, but designed for humans. - * It should be checked into source control and tracked carefully. - * Re-generate by setting TAP_SNAPSHOT=1 and running tests. - * Make sure to inspect the output below. Do not ignore changes! - */ -'use strict' -exports[`test/snapshot/linkThing.js > TAP > linkThing (snapshot) > basic behavior 1`] = ` -<a style="--primary-color: #abcdef" href="track/foo/">Cool track!</a> -` - -exports[`test/snapshot/linkThing.js > TAP > linkThing (snapshot) > color 1`] = ` -<a href="track/showtime-piano-refrain/">Showtime (Piano Refrain)</a> -<a style="--primary-color: #38f43d" href="track/showtime-piano-refrain/">Showtime (Piano Refrain)</a> -<a style="--primary-color: #aaccff" href="track/showtime-piano-refrain/">Showtime (Piano Refrain)</a> -<span style="--primary-color: #aaccff" class="text-with-tooltip"><span class="hoverable"><a class="text-with-tooltip-interaction-cue" href="track/showtime-piano-refrain/">Showtime (Piano Refrain)</a></span><span class="tooltip thing-name-tooltip"><span class="tooltip-content">Showtime (Piano Refrain)</span></span></span> -` - -exports[`test/snapshot/linkThing.js > TAP > linkThing (snapshot) > nested links in content stripped 1`] = ` -<a href="foo/"><b>Oooo! Very spooky.</b></a> -` - -exports[`test/snapshot/linkThing.js > TAP > linkThing (snapshot) > preferShortName 1`] = ` -<span class="text-with-tooltip"><span class="hoverable"><a class="text-with-tooltip-interaction-cue" href="tag/five-oceanfalls/">Five</a></span><span class="tooltip thing-name-tooltip"><span class="tooltip-content">Five (Oceanfalls)</span></span></span> -` - -exports[`test/snapshot/linkThing.js > TAP > linkThing (snapshot) > tags in name escaped 1`] = ` -<a href="track/foo/"><a href="SNOOPING">AS USUAL</a> I SEE</a> -<a href="track/bar/"><b>boldface</b></a> -<a href="album/exile/">>Exile<</a> -<a href="track/heart/"><3</a> -` - -exports[`test/snapshot/linkThing.js > TAP > linkThing (snapshot) > tooltip & content 1`] = ` -<a href="album/beyond-canon/">Beyond Canon</a> -<a title="Beyond Canon" href="album/beyond-canon/">Beyond Canon</a> -<a title="Beyond Canon" href="album/beyond-canon/">Next</a> -<a href="album/beyond-canon/">Beyond Canon</a> -<span class="text-with-tooltip"><span class="hoverable"><a class="text-with-tooltip-interaction-cue" href="album/beyond-canon/">BC</a></span><span class="tooltip thing-name-tooltip"><span class="tooltip-content">Beyond Canon</span></span></span> -<span class="text-with-tooltip"><span class="hoverable"><a class="text-with-tooltip-interaction-cue" href="album/beyond-canon/">Next</a></span><span class="tooltip thing-name-tooltip"><span class="tooltip-content">Beyond Canon</span></span></span> -<a href="album/beyond-canon/">Next</a> -<span class="text-with-tooltip"><span class="hoverable"><a class="text-with-tooltip-interaction-cue" href="album/beyond-canon/">Beyond Canon</a></span><span class="tooltip thing-name-tooltip"><span class="tooltip-content">Beyond Canon</span></span></span> -<span class="text-with-tooltip"><span class="hoverable"><a class="text-with-tooltip-interaction-cue" href="album/beyond-canon/">Next</a></span><span class="tooltip thing-name-tooltip"><span class="tooltip-content">Beyond Canon</span></span></span> -<a href="album/beyond-canon/">Banana</a> -` diff --git a/tap-snapshots/test/snapshot/transformContent.js.test.cjs b/tap-snapshots/test/snapshot/transformContent.js.test.cjs deleted file mode 100644 index ac23a1bf..00000000 --- a/tap-snapshots/test/snapshot/transformContent.js.test.cjs +++ /dev/null @@ -1,142 +0,0 @@ -/* IMPORTANT - * This snapshot file is auto-generated, but designed for humans. - * It should be checked into source control and tracked carefully. - * Re-generate by setting TAP_SNAPSHOT=1 and running tests. - * Make sure to inspect the output below. Do not ignore changes! - */ -'use strict' -exports[`test/snapshot/transformContent.js > TAP > transformContent (snapshot) > absorb punctuation 1`] = ` -<p>Don't you think this is an <a class="external-link from-content indicate-external" href="https://github.com/hsmusic/hsmusic-wiki/pull/567" title="github.com (opens in new tab)" target="_blank">interesting pull request<span class="normal-content">,</span></a> Steve?</p> -<p>Aren't you <a class="external-link from-content indicate-external" href="https://github.com/hsmusic/hsmusic-wiki/pull/567" title="github.com (opens in new tab)" target="_blank">interested<span class="normal-content">...</span></a> in <a class="external-link from-content indicate-external" href="https://github.com/hsmusic/hsmusic-wiki/pull/567" title="github.com (opens in new tab)" target="_blank">checking it out<span class="normal-content">?!!</span></a></p> -` - -exports[`test/snapshot/transformContent.js > TAP > transformContent (snapshot) > basic markdown 1`] = ` -<p>Hello <em>world!</em> This is <strong>SO COOL.</strong></p> -` - -exports[`test/snapshot/transformContent.js > TAP > transformContent (snapshot) > dates 1`] = ` -<p><time datetime="Thu, 13 Apr 2023 00:00:00 GMT">4/12/2023</time> Yep!</p> -<p>Very nice: <time datetime="Fri, 25 Oct 2413 03:00:00 GMT">10/25/2413</time></p> -` - -exports[`test/snapshot/transformContent.js > TAP > transformContent (snapshot) > emails 1`] = ` -<p>Email cute dogs to qznebula@protonmail.com please.</p> -<p>Just kidding... <a class="external-link from-content indicate-external" href="mailto:qznebula@protonmail.com" title="External (opens in new tab)" target="_blank">unless?</a></p> -` - -exports[`test/snapshot/transformContent.js > TAP > transformContent (snapshot) > escape end of tag 1`] = ` -<p>My favorite album is <a style="--primary-color: #123456" href="to-localized.album/cool-album">[Tactical Omission]</a>.</p> -<p>Your favorite album is <a style="--primary-color: #123456" href="to-localized.album/cool-album">[Tactical Wha-Huh-Now</a>].</p> -` - -exports[`test/snapshot/transformContent.js > TAP > transformContent (snapshot) > escape entire tag 1`] = ` -<p>[[album:cool-album|spooky]] <a style="--primary-color: #123456" href="to-localized.album/cool-album">scary</a></p> -` - -exports[`test/snapshot/transformContent.js > TAP > transformContent (snapshot) > escape markdown 1`] = ` -<p>What will it be, <em>ye fool?</em> *arr*</p> -` - -exports[`test/snapshot/transformContent.js > TAP > transformContent (snapshot) > hanging indent list 1`] = ` -<p>Hello!</p> -<ul> -<li><p>I am a list item and I -go on and on and on -and on and on and on.</p> -</li> -<li><p>I am another list item. -Yeah.</p> -</li> -</ul> -<p>In-between!</p> -<ul> -<li>Spooky, -spooky, I say!</li> -<li>Following list item. -No empty line around me.</li> -<li>Very cool. -So, so cool.</li> -</ul> -<p>Goodbye!</p> -` - -exports[`test/snapshot/transformContent.js > TAP > transformContent (snapshot) > indent on a directly following line 1`] = ` -<div> - <span>Wow!</span> -</div> -` - -exports[`test/snapshot/transformContent.js > TAP > transformContent (snapshot) > indent on an indierctly following line 1`] = ` -<p>Some text.</p> -<p>Yes, some more text.</p> -<pre><code>I am hax0rz!! -All yor base r blong 2 us. -</code></pre> -<p>Aye.</p> -<p>Aye aye aye.</p> -` - -exports[`test/snapshot/transformContent.js > TAP > transformContent (snapshot) > inline images 1`] = ` -<p><img src="snooping.png"> as USUAL...</p> -<p>What do you know? <img src="cowabunga.png" width="24" height="32"></p> -<p><a style="--primary-color: #123456" href="to-localized.album/cool-album">I'm on the left.</a><img src="im-on-the-right.jpg"></p> -<p><img src="im-on-the-left.jpg"><a style="--primary-color: #123456" href="to-localized.album/cool-album">I'm on the right.</a></p> -<p>Media time! <img src="to-media.path/misc/interesting.png"> Oh yeah!</p> -<p><img src="must.png"><img src="stick.png"><img src="together.png"></p> -<p>And... all done! <img src="end-of-source.png"></p> -` - -exports[`test/snapshot/transformContent.js > TAP > transformContent (snapshot) > links to a thing 1`] = ` -<p>This is <a style="--primary-color: #123456" href="to-localized.album/cool-album">my favorite album</a>.</p> -<p>That's right, <a style="--primary-color: #123456" href="to-localized.album/cool-album">Cool Album</a>!</p> -` - -exports[`test/snapshot/transformContent.js > TAP > transformContent (snapshot) > lyrics - basic line breaks 1`] = ` -<p>Hey, ho<br> -And away we go<br> -Truly, music</p> -<p>(Oh yeah)<br> -(That's right)</p> -` - -exports[`test/snapshot/transformContent.js > TAP > transformContent (snapshot) > lyrics - line breaks around tags 1`] = ` -<p>The date be <time datetime="Tue, 13 Apr 2004 03:00:00 GMT">4/13/2004</time><br> -I say, the date be <time datetime="Tue, 13 Apr 2004 03:00:00 GMT">4/13/2004</time><br> -<time datetime="Tue, 13 Apr 2004 03:00:00 GMT">4/13/2004</time><br> -<time datetime="Tue, 13 Apr 2004 03:00:00 GMT">4/13/2004</time><time datetime="Tue, 13 Apr 2004 03:00:00 GMT">4/13/2004</time><time datetime="Tue, 13 Apr 2004 03:00:00 GMT">4/13/2004</time><br> -(Aye!)</p> -<p><time datetime="Tue, 13 Apr 2004 03:00:00 GMT">4/13/2004</time><br> -<time datetime="Tue, 13 Apr 2004 03:00:00 GMT">4/13/2004</time><time datetime="Tue, 13 Apr 2004 03:00:00 GMT">4/13/2004</time><br> -<time datetime="Tue, 13 Apr 2004 03:00:00 GMT">4/13/2004</time><br></p> -<p><time datetime="Tue, 13 Apr 2004 03:00:00 GMT">4/13/2004</time><br> -<time datetime="Tue, 13 Apr 2004 03:00:00 GMT">4/13/2004</time>, and don't ye forget it</p> -` - -exports[`test/snapshot/transformContent.js > TAP > transformContent (snapshot) > lyrics - repeated and edge line breaks 1`] = ` -<p>Well, you know<br> -How it goes</p> -<p>Yessiree</p> -` - -exports[`test/snapshot/transformContent.js > TAP > transformContent (snapshot) > non-inline image #1 1`] = ` -<div class="content-image-container">[mocked: image - slots: { src: 'spark.png', link: true, thumb: 'large', attributes: [ { class: 'content-image' }, undefined ] }]</div> -` - -exports[`test/snapshot/transformContent.js > TAP > transformContent (snapshot) > non-inline image #2 1`] = ` -<p>Rad.</p> -<div class="content-image-container">[mocked: image - slots: { src: 'spark.png', link: true, thumb: 'large', attributes: [ { class: 'content-image' }, undefined ] }]</div> -` - -exports[`test/snapshot/transformContent.js > TAP > transformContent (snapshot) > non-inline image #3 1`] = ` -<div class="content-image-container">[mocked: image - slots: { src: 'spark.png', link: true, thumb: 'large', attributes: [ { class: 'content-image' }, undefined ] }]</div> -<p>Baller.</p> -` - -exports[`test/snapshot/transformContent.js > TAP > transformContent (snapshot) > super basic string 1`] = ` -<p>Neat listing: Albums - by Date</p> -` - -exports[`test/snapshot/transformContent.js > TAP > transformContent (snapshot) > two text paragraphs 1`] = ` -<p>Hello, world!</p> -<p>Wow, this is very cool.</p> -` diff --git a/test/lib/content-function.js b/test/lib/content-function.js index a46d18c9..49fe5c95 100644 --- a/test/lib/content-function.js +++ b/test/lib/content-function.js @@ -11,16 +11,21 @@ import {quickEvaluate} from '#content-function'; import * as html from '#html'; import {internalDefaultStringsFile, processLanguageFile} from '#language'; import {empty} from '#sugar'; -import {generateURLs, thumb, urlSpec} from '#urls'; + +import { + applyLocalizedWithBaseDirectory, + generateURLs, + internalDefaultURLSpecFile, + processURLSpecFromFileSync, + thumb, +} from '#urls'; import mock from './generic-mock.js'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); -function cleanURLSpec(reference) { - const prepared = structuredClone(reference); - - for (const spec of Object.values(prepared)) { +function cleanURLSpec(urlSpec) { + for (const spec of Object.values(urlSpec)) { if (spec.prefix) { // Strip out STATIC_VERSION. This updates fairly regularly and we // don't want it to affect snapshot tests. @@ -28,12 +33,23 @@ function cleanURLSpec(reference) { .replace(/static-\d+[a-z]\d+/i, 'static'); } } +} + +function urlsPlease() { + const {aggregate: urlsAggregate, result: urlSpec} = + processURLSpecFromFileSync(internalDefaultURLSpecFile); + + urlsAggregate.close(); + + applyLocalizedWithBaseDirectory(urlSpec); + + cleanURLSpec(urlSpec); - return prepared; + return generateURLs(urlSpec); } export function testContentFunctions(t, message, fn) { - const urls = generateURLs(cleanURLSpec(urlSpec)); + const urls = urlsPlease(); t.test(message, async t => { let loadedContentDependencies; diff --git a/test/lib/wiki-data.js b/test/lib/wiki-data.js index 7c3d2147..f0ee0ef5 100644 --- a/test/lib/wiki-data.js +++ b/test/lib/wiki-data.js @@ -1,5 +1,6 @@ import CacheableObject from '#cacheable-object'; -import find from '#find'; +import find, {bindFind} from '#find'; +import {bindReverse} from '#reverse'; import {withEntries} from '#sugar'; import Thing from '#thing'; import thingConstructors from '#things'; @@ -9,11 +10,13 @@ export function linkAndBindWikiData(wikiData, { inferAlbumsOwnTrackData = true, } = {}) { function customLinkWikiDataArrays(wikiData, options = {}) { - linkWikiDataArrays( - (options.XXX_decacheWikiData - ? withEntries(wikiData, entries => entries - .map(([key, value]) => [key, value.slice()])) - : wikiData)); + if (options.XXX_decacheWikiData) { + wikiData = + withEntries(wikiData, entries => entries + .map(([key, value]) => [key, value.slice()])); + } + + linkWikiDataArrays(wikiData, {bindFind, bindReverse}); } customLinkWikiDataArrays(wikiData); diff --git a/test/snapshot/generateAlbumAdditionalFilesList.js b/test/snapshot/generateAlbumAdditionalFilesList.js deleted file mode 100644 index c25e5682..00000000 --- a/test/snapshot/generateAlbumAdditionalFilesList.js +++ /dev/null @@ -1,84 +0,0 @@ -import t from 'tap'; - -import {testContentFunctions} from '#test-lib'; -import thingConstructors from '#things'; - -const {Album} = thingConstructors; - -testContentFunctions(t, 'generateAlbumAdditionalFilesList (snapshot)', async (t, evaluate) => { - const sizeMap = { - 'sburbwp_1280x1024.jpg': 2500, - 'sburbwp_1440x900.jpg': null, - 'sburbwp_1920x1080.jpg': null, - 'Internet Explorer.gif': 1, - 'Homestuck_Vol4_alt1.jpg': 1234567, - 'Homestuck_Vol4_alt2.jpg': 1234567, - 'Homestuck_Vol4_alt3.jpg': 1234567, - }; - - const extraDependencies = { - getSizeOfAdditionalFile: file => - Object.entries(sizeMap) - .find(key => file.includes(key)) - ?.at(1) ?? null, - }; - - await evaluate.load({ - mock: { - image: evaluate.stubContentFunction('image'), - }, - }); - - const album = new Album(); - album.directory = 'exciting-album'; - - evaluate.snapshot('no additional files', { - extraDependencies, - name: 'generateAlbumAdditionalFilesList', - args: [album, []], - }); - - try { - evaluate.snapshot('basic behavior', { - extraDependencies, - name: 'generateAlbumAdditionalFilesList', - args: [ - album, - [ - { - title: 'SBURB Wallpaper', - files: [ - 'sburbwp_1280x1024.jpg', - 'sburbwp_1440x900.jpg', - 'sburbwp_1920x1080.jpg', - ], - }, - { - title: 'Fake Section', - description: 'No sizes for these files', - files: [ - 'oops.mp3', - 'Internet Explorer.gif', - 'daisy.mp3', - ], - }, - { - title: `Empty Section`, - description: `These files haven't been made available.`, - }, - { - title: 'Alternate Covers', - description: 'This is just an example description.', - files: [ - 'Homestuck_Vol4_alt1.jpg', - 'Homestuck_Vol4_alt2.jpg', - 'Homestuck_Vol4_alt3.jpg', - ], - }, - ], - ], - }); - } catch (error) { - console.log(error); - } -}); diff --git a/test/snapshot/generateAlbumBanner.js b/test/snapshot/generateAlbumBanner.js deleted file mode 100644 index 8e63308f..00000000 --- a/test/snapshot/generateAlbumBanner.js +++ /dev/null @@ -1,34 +0,0 @@ -import t from 'tap'; -import {testContentFunctions} from '#test-lib'; - -testContentFunctions(t, 'generateAlbumBanner (snapshot)', async (t, evaluate) => { - await evaluate.load(); - - evaluate.snapshot('basic behavior', { - name: 'generateAlbumBanner', - args: [{ - directory: 'cool-album', - hasBannerArt: true, - bannerDimensions: [800, 200], - bannerFileExtension: 'png', - }], - }); - - evaluate.snapshot('no dimensions', { - name: 'generateAlbumBanner', - args: [{ - directory: 'cool-album', - hasBannerArt: true, - bannerDimensions: null, - bannerFileExtension: 'png', - }], - }); - - evaluate.snapshot('no banner', { - name: 'generateAlbumBanner', - args: [{ - directory: 'cool-album', - hasBannerArt: false, - }], - }); -}); diff --git a/test/snapshot/generateAlbumReleaseInfo.js b/test/snapshot/generateAlbumReleaseInfo.js deleted file mode 100644 index f41e502d..00000000 --- a/test/snapshot/generateAlbumReleaseInfo.js +++ /dev/null @@ -1,74 +0,0 @@ -import t from 'tap'; -import {testContentFunctions} from '#test-lib'; - -testContentFunctions(t, 'generateAlbumReleaseInfo (snapshot)', async (t, evaluate) => { - await evaluate.load(); - - evaluate.snapshot('basic behavior', { - name: 'generateAlbumReleaseInfo', - args: [{ - artistContribs: [ - {artist: {name: 'Toby Fox', directory: 'toby-fox', urls: []}, annotation: 'music probably'}, - {artist: {name: 'Tensei', directory: 'tensei', urls: ['https://tenseimusic.bandcamp.com/']}, annotation: 'hot jams'}, - ], - - coverArtistContribs: [ - {artist: {name: 'Hanni Brosh', directory: 'hb', urls: []}, annotation: null}, - ], - - wallpaperArtistContribs: [ - {artist: {name: 'Hanni Brosh', directory: 'hb', urls: []}, annotation: null}, - {artist: {name: 'Niklink', directory: 'niklink', urls: []}, annotation: 'edits'}, - ], - - bannerArtistContribs: [ - {artist: {name: 'Hanni Brosh', directory: 'hb', urls: []}, annotation: null}, - {artist: {name: 'Niklink', directory: 'niklink', urls: []}, annotation: 'edits'}, - ], - - name: 'AlterniaBound', - date: new Date('March 14, 2011'), - coverArtDate: new Date('April 1, 1991'), - urls: [ - 'https://homestuck.bandcamp.com/album/alterniabound-with-alternia', - 'https://www.youtube.com/playlist?list=PLnVpmehyaOFZWO9QOZmD6A3TIK0wZ6xE2', - 'https://www.youtube.com/watch?v=HO5V2uogkYc', - ], - - tracks: [{duration: 253}, {duration: 372}], - }], - }); - - const sparse = { - artistContribs: [], - coverArtistContribs: [], - wallpaperArtistContribs: [], - bannerArtistContribs: [], - - name: 'Suspicious Album', - urls: [], - tracks: [], - }; - - evaluate.snapshot('reduced details', { - name: 'generateAlbumReleaseInfo', - args: [sparse], - }); - - evaluate.snapshot('URLs only', { - name: 'generateAlbumReleaseInfo', - args: [{ - ...sparse, - urls: ['https://homestuck.bandcamp.com/foo', 'https://soundcloud.com/bar'], - }], - }); - - evaluate.snapshot('equal cover art date', { - name: 'generateAlbumReleaseInfo', - args: [{ - ...sparse, - date: new Date('2020-04-13'), - coverArtDate: new Date('2020-04-13'), - }], - }); -}); diff --git a/test/snapshot/generateAlbumSecondaryNav.js b/test/snapshot/generateAlbumSecondaryNav.js deleted file mode 100644 index 2495bc4a..00000000 --- a/test/snapshot/generateAlbumSecondaryNav.js +++ /dev/null @@ -1,90 +0,0 @@ -import t from 'tap'; -import {testContentFunctions} from '#test-lib'; - -testContentFunctions(t, 'generateAlbumSecondaryNav (snapshot)', async (t, evaluate) => { - await evaluate.load(); - - let album, anotherAlbum, group1, group2; - - group1 = { - name: 'VCG', - directory: 'vcg', - color: '#abcdef', - serieses: [], - }; - - group2 = { - name: 'Bepis', - directory: 'bepis', - color: '#123456', - serieses: [], - }; - - album = { - name: 'Album', - directory: 'album', - date: new Date('2010-04-13'), - groups: [group1, group2], - }; - - anotherAlbum = { - name: 'Last', - directory: 'last', - date: new Date('2010-06-12'), - }; - - group1.albums = [ - { - name: 'First', - directory: 'first', - date: new Date('2010-04-10'), - }, - album, - anotherAlbum, - ]; - - group1.serieses = [ - { - name: 'Series', - albums: [album, anotherAlbum], - group: group1, - }, - ]; - - group2.albums = [ - album, - { - name: 'Second', - directory: 'second', - date: new Date('2011-04-13'), - }, - ]; - - evaluate.snapshot('basic behavior, mode: album', { - name: 'generateAlbumSecondaryNav', - args: [album], - slots: {mode: 'album'}, - }); - - evaluate.snapshot('basic behavior, mode: track', { - name: 'generateAlbumSecondaryNav', - args: [album], - slots: {mode: 'track'}, - }); - - album = { - date: null, - groups: [group1, group2], - }; - - group1.albums = [ - ...group1.albums, - album, - ]; - - evaluate.snapshot('dateless album in mixed group', { - name: 'generateAlbumSecondaryNav', - args: [album], - slots: {mode: 'album'}, - }); -}); diff --git a/test/snapshot/generateAlbumSidebarGroupBox.js b/test/snapshot/generateAlbumSidebarGroupBox.js deleted file mode 100644 index f920bd96..00000000 --- a/test/snapshot/generateAlbumSidebarGroupBox.js +++ /dev/null @@ -1,57 +0,0 @@ -import t from 'tap'; -import {testContentFunctions} from '#test-lib'; - -testContentFunctions(t, 'generateAlbumSidebarGroupBox (snapshot)', async (t, evaluate) => { - await evaluate.load({ - mock: { - ...evaluate.mock.transformContent, - }, - }); - - let album, group; - - album = { - name: 'Middle', - directory: 'middle', - date: new Date('2010-04-13'), - }; - - group = { - name: 'VCG', - directory: 'vcg', - descriptionShort: 'Very cool group.', - urls: ['https://vcg.bandcamp.com/', 'https://youtube.com/@vcg'], - albums: [ - {name: 'First', directory: 'first', date: new Date('2010-04-10')}, - album, - {name: 'Last', directory: 'last', date: new Date('2010-06-12')}, - ], - }; - - evaluate.snapshot('basic behavior, mode: album', { - name: 'generateAlbumSidebarGroupBox', - args: [album, group], - slots: {mode: 'album'}, - }); - - evaluate.snapshot('basic behavior, mode: track', { - name: 'generateAlbumSidebarGroupBox', - args: [album, group], - slots: {mode: 'track'}, - }); - - album = { - date: null, - }; - - group.albums = [ - ...group.albums, - album, - ]; - - evaluate.snapshot('dateless album in mixed group', { - name: 'generateAlbumSidebarGroupBox', - args: [album, group], - slots: {mode: 'album'}, - }); -}); diff --git a/test/snapshot/generateAlbumTrackList.js b/test/snapshot/generateAlbumTrackList.js deleted file mode 100644 index e09f1c4a..00000000 --- a/test/snapshot/generateAlbumTrackList.js +++ /dev/null @@ -1,123 +0,0 @@ -import t from 'tap'; -import {testContentFunctions} from '#test-lib'; - -testContentFunctions(t, 'generateAlbumTrackList (snapshot)', async (t, evaluate) => { - await evaluate.load({ - mock: { - generateAlbumTrackListMissingDuration: - evaluate.stubContentFunction('generateAlbumTrackListMissingDuration'), - - image: - evaluate.stubContentFunction('image'), - }, - }); - - const contribs1 = [ - {artist: {name: 'Apricot', directory: 'apricot', urls: []}}, - ]; - - const contribs2 = [ - {artist: {name: 'Apricot', directory: 'apricot', urls: []}}, - {artist: {name: 'Peach', directory: 'peach', urls: ['https://peach.bandcamp.com/']}}, - {artist: {name: 'Cerise', directory: 'cerise', urls: []}}, - ]; - - const color1 = '#fb07ff'; - const color2 = '#ea2e83'; - - const tracks = [ - {name: 'Track 1', directory: 't1', duration: 20, artistContribs: contribs1, color: color1}, - {name: 'Track 2', directory: 't2', duration: 0, artistContribs: contribs1, color: color1}, - {name: 'Track 3', directory: 't3', duration: 40, artistContribs: contribs1, color: color1}, - {name: 'Track 4', directory: 't4', duration: 0, artistContribs: contribs2, color: color2}, - ]; - - const albumWithTrackSections = { - color: color1, - artistContribs: contribs1, - trackSections: [ - {name: 'First section', tracks: tracks.slice(0, 3)}, - {name: 'Second section', tracks: tracks.slice(3)}, - ], - tracks, - }; - - const albumWithoutTrackSections = { - color: color1, - artistContribs: contribs1, - trackSections: [{isDefaultTrackSection: true, tracks}], - tracks, - }; - - const albumWithTrackSectionDescriptions = { - color: color1, - artistContribs: contribs1, - trackSections: [ - {name: 'First section', tracks: tracks.slice(0, 3), description: `Why yes!`}, - {name: 'Second section', tracks: tracks.slice(3), description: `How *contentful,* this is.`}, - ], - tracks, - }; - - const albumWithNoDuration = { - color: color1, - artistContribs: contribs1, - trackSections: [{isDefaultTrackSection: true, tracks: [tracks[1], tracks[3]]}], - tracks: [tracks[1], tracks[3]], - }; - - evaluate.snapshot(`basic behavior, with track sections`, { - name: 'generateAlbumTrackList', - args: [albumWithTrackSections], - }); - - evaluate.snapshot(`basic behavior, default track section`, { - name: 'generateAlbumTrackList', - args: [albumWithoutTrackSections], - }); - - evaluate.snapshot(`basic behavior, with descriptions`, { - name: 'generateAlbumTrackList', - args: [albumWithTrackSectionDescriptions], - }); - - evaluate.snapshot(`collapseDurationScope: never`, { - name: 'generateAlbumTrackList', - slots: {collapseDurationScope: 'never'}, - multiple: [ - {args: [albumWithTrackSections]}, - {args: [albumWithoutTrackSections]}, - {args: [albumWithNoDuration]}, - ], - }); - - evaluate.snapshot(`collapseDurationScope: track`, { - name: 'generateAlbumTrackList', - slots: {collapseDurationScope: 'track'}, - multiple: [ - {args: [albumWithTrackSections]}, - {args: [albumWithoutTrackSections]}, - {args: [albumWithNoDuration]}, - ], - }); - - evaluate.snapshot(`collapseDurationScope: section`, { - name: 'generateAlbumTrackList', - slots: {collapseDurationScope: 'section'}, - multiple: [ - {args: [albumWithTrackSections]}, - {args: [albumWithoutTrackSections]}, - {args: [albumWithNoDuration]}, - ], - }); - - evaluate.snapshot(`collapseDurationScope: album`, { - name: 'generateAlbumTrackList', - slots: {collapseDurationScope: 'album'}, - multiple: [ - {args: [albumWithTrackSections]}, - {args: [albumWithoutTrackSections]}, - {args: [albumWithNoDuration]}, - ], - }); -}); diff --git a/test/snapshot/generateBanner.js b/test/snapshot/generateBanner.js deleted file mode 100644 index ab57c3cc..00000000 --- a/test/snapshot/generateBanner.js +++ /dev/null @@ -1,22 +0,0 @@ -import t from 'tap'; -import {testContentFunctions} from '#test-lib'; - -testContentFunctions(t, 'generateBanner (snapshot)', async (t, evaluate) => { - await evaluate.load(); - - evaluate.snapshot('basic behavior', { - name: 'generateBanner', - slots: { - path: ['media.albumBanner', 'cool-album', 'png'], - alt: 'Very cool banner art.', - dimensions: [800, 200], - }, - }); - - evaluate.snapshot('no dimensions', { - name: 'generateBanner', - slots: { - path: ['media.albumBanner', 'cool-album', 'png'], - }, - }); -}); diff --git a/test/snapshot/generateTrackReleaseInfo.js b/test/snapshot/generateTrackReleaseInfo.js deleted file mode 100644 index 931377c8..00000000 --- a/test/snapshot/generateTrackReleaseInfo.js +++ /dev/null @@ -1,51 +0,0 @@ -import t from 'tap'; -import {testContentFunctions} from '#test-lib'; - -testContentFunctions(t, 'generateTrackReleaseInfo (snapshot)', async (t, evaluate) => { - await evaluate.load(); - - const artistContribs = [{artist: {name: 'Toby Fox', directory: 'toby-fox', urls: []}, annotation: null}]; - const coverArtistContribs = [{artist: {name: 'Alpaca', directory: 'alpaca', urls: []}, annotation: '🔥'}]; - - evaluate.snapshot('basic behavior', { - name: 'generateTrackReleaseInfo', - args: [{ - artistContribs, - name: 'An Apple Disaster!!', - date: new Date('2011-11-30'), - duration: 58, - urls: ['https://soundcloud.com/foo', 'https://youtube.com/watch?v=bar'], - }], - }); - - const sparse = { - artistContribs, - name: 'Suspicious Track', - date: null, - duration: null, - urls: [], - }; - - evaluate.snapshot('reduced details', { - name: 'generateTrackReleaseInfo', - args: [sparse], - }); - - evaluate.snapshot('cover artist contribs, non-unique', { - name: 'generateTrackReleaseInfo', - args: [{ - ...sparse, - coverArtistContribs, - hasUniqueCoverArt: false, - }], - }); - - evaluate.snapshot('cover artist contribs, unique', { - name: 'generateTrackReleaseInfo', - args: [{ - ...sparse, - coverArtistContribs, - hasUniqueCoverArt: true, - }], - }); -}); diff --git a/test/snapshot/image.js b/test/snapshot/image.js deleted file mode 100644 index 1985211f..00000000 --- a/test/snapshot/image.js +++ /dev/null @@ -1,132 +0,0 @@ -import t from 'tap'; -import {testContentFunctions} from '#test-lib'; - -testContentFunctions(t, 'image (snapshot)', async (t, evaluate) => { - await evaluate.load(); - - const quickSnapshot = (message, {extraDependencies, ...opts}) => - evaluate.snapshot(message, { - name: 'image', - extraDependencies: { - checkIfImagePathHasCachedThumbnails: path => !path.endsWith('.gif'), - getSizeOfImagePath: () => 0, - getDimensionsOfImagePath: () => [600, 600], - getThumbnailEqualOrSmaller: () => 'medium', - getThumbnailsAvailableForDimensions: () => - [['large', 800], ['medium', 400], ['small', 250]], - missingImagePaths: ['album-art/missing/cover.png'], - ...extraDependencies, - }, - ...opts, - }); - - quickSnapshot('source via path', { - slots: { - path: ['media.albumCover', 'beyond-canon', 'png'], - }, - }); - - quickSnapshot('source via src', { - slots: { - src: 'https://example.com/bananas.gif', - }, - }); - - quickSnapshot('source missing', { - slots: { - missingSourceContent: 'Example of missing source message.', - }, - }); - - quickSnapshot('dimensions', { - slots: { - src: 'foobar', - dimensions: [600, 400], - }, - }); - - quickSnapshot('square', { - slots: { - src: 'foobar', - square: true, - }, - }); - - quickSnapshot('dimensions with square', { - slots: { - src: 'foobar', - dimensions: [600, 400], - square: true, - }, - }); - - quickSnapshot('lazy with square', { - slots: { - src: 'foobar', - lazy: true, - square: true, - }, - }); - - quickSnapshot('link with file size', { - extraDependencies: { - getSizeOfImagePath: () => 10 ** 6, - }, - slots: { - path: ['media.albumCover', 'pingas', 'png'], - link: true, - }, - }); - - quickSnapshot('content warnings via tags', { - args: [ - [ - {name: 'Dirk Strider', directory: 'dirk'}, - {name: 'too cool for school', isContentWarning: true}, - ], - ], - slots: { - path: ['media.albumCover', 'beyond-canon', 'png'], - }, - }); - - evaluate.snapshot('thumbnail details', { - name: 'image', - extraDependencies: { - checkIfImagePathHasCachedThumbnails: () => true, - getSizeOfImagePath: () => 0, - getDimensionsOfImagePath: () => [900, 1200], - getThumbnailsAvailableForDimensions: () => - [['voluminous', 1200], ['middling', 900], ['petite', 20]], - getThumbnailEqualOrSmaller: () => 'voluminous', - missingImagePaths: [], - }, - slots: { - thumb: 'gargantuan', - path: ['media.albumCover', 'beyond-canon', 'png'], - }, - }); - - quickSnapshot('thumb requested but source is gif', { - slots: { - thumb: 'medium', - path: ['media.flashArt', '5426', 'gif'], - }, - }); - - quickSnapshot('missing image path', { - slots: { - thumb: 'medium', - path: ['media.albumCover', 'missing', 'png'], - link: true, - }, - }); - - quickSnapshot('missing image path w/ missingSourceContent', { - slots: { - thumb: 'medium', - path: ['media.albumCover', 'missing', 'png'], - missingSourceContent: `Cover's missing, whoops`, - }, - }); -}); diff --git a/test/snapshot/linkArtist.js b/test/snapshot/linkArtist.js deleted file mode 100644 index 7b2114b5..00000000 --- a/test/snapshot/linkArtist.js +++ /dev/null @@ -1,30 +0,0 @@ -import t from 'tap'; -import {testContentFunctions} from '#test-lib'; - -testContentFunctions(t, 'linkArtist (snapshot)', async (t, evaluate) => { - await evaluate.load(); - - evaluate.snapshot('basic behavior', { - name: 'linkArtist', - args: [ - { - name: `Toby Fox`, - directory: `toby-fox`, - } - ], - }); - - evaluate.snapshot('prefer short name', { - name: 'linkArtist', - args: [ - { - name: 'ICCTTCMDMIROTMCWMWFTPFTDDOTARHPOESWGBTWEATFCWSEBTSSFOFG', - nameShort: '55gore', - directory: '55gore', - }, - ], - slots: { - preferShortName: true, - }, - }); -}); diff --git a/test/snapshot/linkContribution.js b/test/snapshot/linkContribution.js deleted file mode 100644 index 47ef1503..00000000 --- a/test/snapshot/linkContribution.js +++ /dev/null @@ -1,73 +0,0 @@ -import t from 'tap'; -import {testContentFunctions} from '#test-lib'; - -testContentFunctions(t, 'linkContribution (snapshot)', async (t, evaluate) => { - await evaluate.load(); - - const quickSnapshot = (message, slots) => - evaluate.snapshot(message, { - name: 'linkContribution', - multiple: [ - {args: [ - {artist: { - name: 'Clark Powell', - directory: 'clark-powell', - urls: ['https://soundcloud.com/plazmataz'], - }, annotation: null}, - ]}, - {args: [ - {artist: { - name: 'Grounder & Scratch', - directory: 'the-big-baddies', - urls: [], - }, annotation: 'Snooping'}, - ]}, - {args: [ - {artist: { - name: 'Toby Fox', - directory: 'toby-fox', - urls: ['https://tobyfox.bandcamp.com/', 'https://toby.fox/'], - }, annotation: 'Arrangement'}, - ]}, - ], - slots, - }); - - quickSnapshot('showAnnotation & showExternalLinks', { - showAnnotation: true, - showExternalLinks: true, - }); - - quickSnapshot('only showAnnotation', { - showAnnotation: true, - }); - - quickSnapshot('only showExternalLinks', { - showExternalLinks: true, - }); - - quickSnapshot('no accents', {}); - - evaluate.snapshot('loads of links', { - name: 'linkContribution', - args: [ - {artist: {name: 'Lorem Ipsum Lover', directory: 'lorem-ipsum-lover', urls: [ - 'https://loremipsum.io', - 'https://loremipsum.io/generator/', - 'https://loremipsum.io/#meaning', - 'https://loremipsum.io/#usage-and-examples', - 'https://loremipsum.io/#controversy', - 'https://loremipsum.io/#when-to-use-lorem-ipsum', - 'https://loremipsum.io/#lorem-ipsum-all-the-things', - 'https://loremipsum.io/#original-source', - ]}, annotation: null}, - ], - slots: {showExternalLinks: true}, - }); - - quickSnapshot('no preventWrapping', { - showAnnotation: true, - showExternalLinks: true, - preventWrapping: false, - }); -}); diff --git a/test/snapshot/linkExternal.js b/test/snapshot/linkExternal.js deleted file mode 100644 index 90c98f4b..00000000 --- a/test/snapshot/linkExternal.js +++ /dev/null @@ -1,225 +0,0 @@ -import t from 'tap'; -import {testContentFunctions} from '#test-lib'; - -testContentFunctions(t, 'linkExternal (snapshot)', async (t, evaluate) => { - await evaluate.load(); - - evaluate.snapshot('unknown domain (arbitrary world wide web path)', { - name: 'linkExternal', - args: ['https://snoo.ping.as/usual/i/see/'], - }); - - const urlsToArgs = urls => - urls.map(url => ({args: [url]})); - - const quickSnapshot = (message, urls, slots) => - evaluate.snapshot(message, { - name: 'linkExternal', - slots, - multiple: urlsToArgs(urls), - }); - - const quickSnapshotAllStyles = (context, urls) => { - for (const style of ['platform', 'handle']) { - const message = `context: ${context}, style: ${style}`; - quickSnapshot(message, urls, {context, style}); - } - }; - - // Try to comprehensively test every regular expression - // (in `match` and extractions like `handle` or `details`). - - // Try to *also* represent a reasonable variety of what kinds - // of URLs appear throughout the wiki. (This should serve to - // identify areas which #external-links is expected to - // accommodate, regardless whether or not there is special - // attention given in the actual descriptors.) - - // For normal custom-domain matches (e.g. Mastodon), - // it's OK to just test one custom domain in the list. - - // Generally match the sorting order in externalLinkSpec, - // so corresponding and missing test cases are easy to locate. - - quickSnapshotAllStyles('generic', [ - // platform: appleMusic - 'https://music.apple.com/us/artist/system-of-a-down/462715', - - // platform: artstation - 'https://www.artstation.com/eevaningtea', - 'https://witnesstheabsurd.artstation.com/', - - // platform: bandcamp - 'https://music.solatrus.com/', - 'https://homestuck.bandcamp.com/', - - // platform: bluesky - 'https://bsky.app/profile/jacobtheloofah.bsky.social', - - // platform: carrd - 'https://aliceflare.carrd.co', - 'https://bigchaslappa.carrd.co/', - - // platform: cohost - 'https://cohost.org/cosmoptera', - - // platform: deconreconstruction.music - 'https://music.deconreconstruction.com/albums/catch-322', - 'https://music.deconreconstruction.com/albums/catch-322?track=arcjecs-theme', - - // platform: deconreconstruction - 'https://www.deconreconstruction.com/', - - // platform: deviantart - 'https://culdhira.deviantart.com', - 'https://www.deviantart.com/chesswanderlust-sama', - 'https://www.deviantart.com/shilloshilloh/art/Homestuck-Jake-English-268874606', - - // platform: facebook - 'https://www.facebook.com/DoomedCloud/', - 'https://www.facebook.com/pages/WoodenToaster/280642235307371', - 'https://www.facebook.com/Svixy/posts/400018786702633', - - // platform: fandom.mspaintadventures - 'https://mspaintadventures.fandom.com/wiki/Draconian_Dignitary', - 'https://mspaintadventures.fandom.com/wiki/', - 'https://mspaintadventures.fandom.com/', - - // platform: fandom - 'https://community.fandom.com/', - 'https://community.fandom.com/wiki/', - 'https://community.fandom.com/wiki/Community_Central', - - // platform: gamebanana - 'https://gamebanana.com/members/2028092', - 'https://gamebanana.com/mods/459476', - - // platform: homestuck - 'https://homestuck.com/', - - // platform: hsmusic.archive - 'https://hsmusic.wiki/media/misc/archive/Firefly%20Cloud%20Remix.mp3', - - // platform: hsmusic - 'https://hsmusic.wiki/feedback/', - - // platform: internetArchive - 'https://archive.org/details/a-life-well-lived', - 'https://archive.org/details/VastError_Volume1/11+Renaissance.mp3', - - // platform: instagram - 'https://instagram.com/bass.and.noises', - 'https://www.instagram.com/levc_egm/', - - // platform: itch - 'https://tuyoki.itch.io/', - 'https://itch.io/profile/bravelittletoreador', - - // platform: ko-fi - 'https://ko-fi.com/gnaach', - - // platform: linktree - 'https://linktr.ee/bbpanzu', - - // platform: mastodon - 'https://types.pl/', - - // platform: mspfa - 'https://canwc.mspfa.com/', - 'https://mspfa.com/?s=12003&p=1045', - 'https://mspfa.com/user/?u=103334508819793669241', - - // platform: neocities - 'https://wodaro.neocities.org', - 'https://neomints.neocities.org/', - - // platform: newgrounds - 'https://buzinkai.newgrounds.com/', - 'https://www.newgrounds.com/audio/listen/1256058', - - // platform: patreon - 'https://www.patreon.com/CecilyRenns', - - // platform: poetryFoundation - 'https://www.poetryfoundation.org/poets/christina-rossetti', - 'https://www.poetryfoundation.org/poems/45000/remember-56d224509b7ae', - - // platform: soundcloud - 'https://soundcloud.com/plazmataz', - 'https://soundcloud.com/worthikids/1-i-accidentally-broke-my', - - // platform: spotify - 'https://open.spotify.com/artist/63SNNpNOicDzG3LY82G4q3', - 'https://open.spotify.com/album/0iHvPD8rM3hQa0qeVtPQ3t', - 'https://open.spotify.com/track/6YEGQH32aAXb9vQQbBrPlw', - - // platform: tiktok - 'https://www.tiktok.com/@richaadeb', - - // platform: toyhouse - 'https://toyhou.se/ghastaboo', - - // platform: tumblr - 'https://aeritus.tumblr.com/', - 'https://vol5anthology.tumblr.com/post/159528808107/hey-everyone-its-413-and-that-means-we-have', - 'https://www.tumblr.com/electricwestern', - 'https://www.tumblr.com/spellmynamewithabang/142767566733/happy-413-this-is-the-first-time-anyones-heard', - - // platform: twitch - 'https://www.twitch.tv/ajhebard', - 'https://www.twitch.tv/vargskelethor/', - - // platform: twitter - 'https://twitter.com/awkwarddoesart', - 'https://twitter.com/purenonsens/', - 'https://twitter.com/circlejourney/status/1202265927183548416', - - // platform: waybackMachine - 'https://web.archive.org/web/20120405160556/https://homestuck.bandcamp.com/album/colours-and-mayhem-universe-a', - 'https://web.archive.org/web/20160807111207/http://griffinspacejam.com:80/', - - // platform: wikipedia - 'https://en.wikipedia.org/wiki/Haydn_Quartet_(vocal_ensemble)', - - // platform: youtube - 'https://youtube.com/@bani-chan8949', - 'https://www.youtube.com/@Razzie16', - 'https://www.youtube.com/channel/UCQXfvlKkpbOqEz4BepHqK7g', - 'https://www.youtube.com/watch?v=6ekVnZm29kw', - 'https://youtu.be/WBkC038wSio', - 'https://www.youtube.com/playlist?list=PLy5UGIMKOXpONMExgI7lVYFwQa54QFp_H', - ]); - - quickSnapshotAllStyles('album', [ - 'https://youtu.be/abc', - 'https://youtube.com/watch?v=abc', - 'https://youtube.com/Playlist?list=kweh', - ]); - - quickSnapshotAllStyles('albumNoTracks', [ - 'https://youtu.be/abc', - 'https://youtube.com/watch?v=abc', - 'https://youtube.com/Playlist?list=kweh', - ]); - - quickSnapshotAllStyles('albumOneTrack', [ - 'https://youtu.be/abc', - 'https://youtube.com/watch?v=abc', - 'https://youtube.com/Playlist?list=kweh', - ]); - - quickSnapshotAllStyles('albumMultipleTracks', [ - 'https://youtu.be/abc', - 'https://youtube.com/watch?v=abc', - 'https://youtube.com/Playlist?list=kweh', - ]); - - quickSnapshotAllStyles('flash', [ - 'https://www.bgreco.net/hsflash/002238.html', - 'https://homestuck.com/story/1234', - 'https://homestuck.com/story/pony', - 'https://www.youtube.com/watch?v=wKgOp3Kg2wI', - 'https://youtu.be/IOcvkkklWmY', - 'https://some.external.site/foo/bar/', - ]); -}); diff --git a/test/snapshot/linkTemplate.js b/test/snapshot/linkTemplate.js deleted file mode 100644 index 300065e2..00000000 --- a/test/snapshot/linkTemplate.js +++ /dev/null @@ -1,63 +0,0 @@ -import t from 'tap'; -import * as html from '#html'; -import {testContentFunctions} from '#test-lib'; - -testContentFunctions(t, 'linkTemplate (snapshot)', async (t, evaluate) => { - await evaluate.load(); - - evaluate.snapshot('fill many slots', { - name: 'linkTemplate', - - slots: { - 'href': 'https://hsmusic.wiki/media/cool file.pdf', - 'hash': 'fooey', - 'attributes': {class: 'dog', id: 'cat1'}, - 'content': 'My Cool Link', - }, - }); - - evaluate.snapshot('fill path slot & provide appendIndexHTML', { - name: 'linkTemplate', - - extraDependencies: { - to: (...path) => '/c*lzone/' + path.join('/') + '/', - appendIndexHTML: true, - }, - - slots: { - path: ['myCoolPath', 'ham', 'pineapple', 'tomato'], - content: 'delish', - }, - }); - - evaluate.snapshot('special characters in path argument', { - name: 'linkTemplate', - slots: { - path: [ - 'media.albumAdditionalFile', - 'homestuck-vol-1', - 'Showtime (Piano Refrain) - #xXxAwesomeSheetMusick?rxXx#.pdf', - ], - content: `Damn, that's some good sheet music`, - }, - }); - - evaluate.snapshot('missing content', { - name: 'linkTemplate', - slots: {href: 'banana'}, - }); - - evaluate.snapshot('link in content', { - name: 'linkTemplate', - slots: { - hash: 'the-more-ye-know', - content: [ - `Oh geez oh heck`, - html.tag('a', {href: 'dogs'}, `There's a link in here!!`), - `But here's <b>a normal tag.</b>`, - html.tag('div', `Gotta keep them normal tags.`), - html.tag('div', `But not... <a href="#">NESTED LINKS, OOO.</a>`), - ], - }, - }); -}); diff --git a/test/snapshot/linkThing.js b/test/snapshot/linkThing.js deleted file mode 100644 index 9b5cff33..00000000 --- a/test/snapshot/linkThing.js +++ /dev/null @@ -1,94 +0,0 @@ -import t from 'tap'; -import * as html from '#html'; -import {testContentFunctions} from '#test-lib'; - -testContentFunctions(t, 'linkThing (snapshot)', async (t, evaluate) => { - await evaluate.load(); - - const quickSnapshot = (message, oneOrMultiple) => - evaluate.snapshot(message, - (Array.isArray(oneOrMultiple) - ? {name: 'linkThing', multiple: oneOrMultiple} - : {name: 'linkThing', ...oneOrMultiple})); - - quickSnapshot('basic behavior', { - args: ['localized.track', { - directory: 'foo', - color: '#abcdef', - name: `Cool track!`, - }], - }); - - quickSnapshot('preferShortName', { - args: ['localized.artTagGallery', { - directory: 'five-oceanfalls', - name: 'Five (Oceanfalls)', - nameShort: 'Five', - }], - slots: {preferShortName: true}, - }); - - quickSnapshot('tooltip & content', { - args: ['localized.album', { - directory: 'beyond-canon', - name: 'Beyond Canon', - nameShort: 'BC', - }], - multiple: [ - {slots: {tooltipStyle: 'none'}}, - {slots: {tooltipStyle: 'browser'}}, - {slots: {tooltipStyle: 'browser', content: 'Next'}}, - {slots: {tooltipStyle: 'auto'}}, - {slots: {tooltipStyle: 'auto', preferShortName: true}}, - {slots: {tooltipStyle: 'auto', preferShortName: true, content: 'Next'}}, - {slots: {tooltipStyle: 'auto', content: 'Next'}}, - {slots: {tooltipStyle: 'wiki'}}, - {slots: {tooltipStyle: 'wiki', content: 'Next'}}, - {slots: {content: 'Banana'}}, - ], - }); - - quickSnapshot('color', { - args: ['localized.track', { - directory: 'showtime-piano-refrain', - name: 'Showtime (Piano Refrain)', - color: '#38f43d', - }], - multiple: [ - {slots: {color: false}}, - {slots: {color: true}}, - {slots: {color: '#aaccff'}}, - {slots: {color: '#aaccff', tooltipStyle: 'wiki'}}, - ], - }); - - quickSnapshot('tags in name escaped', [ - {args: ['localized.track', { - directory: 'foo', - name: `<a href="SNOOPING">AS USUAL</a> I SEE`, - }]}, - {args: ['localized.track', { - directory: 'bar', - name: `<b>boldface</b>`, - }]}, - {args: ['localized.album', { - directory: 'exile', - name: '>Exile<', - }]}, - {args: ['localized.track', { - directory: 'heart', - name: '<3', - }]}, - ]); - - quickSnapshot('nested links in content stripped', { - args: ['localized.staticPage', {directory: 'foo', name: 'Foo'}], - slots: { - content: - html.tag('b', {[html.joinChildren]: ''}, [ - html.tag('a', {href: 'bar'}, `Oooo!`), - ` Very spooky.`, - ]), - }, - }); -}); diff --git a/test/snapshot/transformContent.js b/test/snapshot/transformContent.js deleted file mode 100644 index 63391681..00000000 --- a/test/snapshot/transformContent.js +++ /dev/null @@ -1,171 +0,0 @@ -import t from 'tap'; -import {testContentFunctions} from '#test-lib'; - -testContentFunctions(t, 'transformContent (snapshot)', async (t, evaluate) => { - await evaluate.load({ - mock: { - image: evaluate.stubContentFunction('image'), - }, - }); - - const extraDependencies = { - wikiData: { - albumData: [ - {directory: 'cool-album', name: 'Cool Album', color: '#123456'}, - ], - }, - - to: (key, ...args) => `to-${key}/${args.join('/')}`, - }; - - const quickSnapshot = (message, content, slots) => - evaluate.snapshot(message, { - name: 'transformContent', - args: [content], - extraDependencies, - slots, - }); - - quickSnapshot( - 'two text paragraphs', - `Hello, world!\n` + - `Wow, this is very cool.`); - - quickSnapshot( - 'links to a thing', - `This is [[album:cool-album|my favorite album]].\n` + - `That's right, [[album:cool-album]]!`); - - quickSnapshot( - 'indent on a directly following line', - `<div>\n` + - ` <span>Wow!</span>\n` + - `</div>`); - - quickSnapshot( - 'indent on an indierctly following line', - `Some text.\n` + - `Yes, some more text.\n` + - `\n` + - ` I am hax0rz!!\n` + - ` All yor base r blong 2 us.\n` + - `\n` + - `Aye.\n` + - `Aye aye aye.`); - - quickSnapshot( - 'hanging indent list', - `Hello!\n` + - `\n` + - `* I am a list item and I\n` + - ` go on and on and on\n` + - ` and on and on and on.\n` + - `\n` + - `* I am another list item.\n` + - ` Yeah.\n` + - `\n` + - `In-between!\n` + - `\n` + - `* Spooky,\n` + - ` spooky, I say!\n` + - `* Following list item.\n` + - ` No empty line around me.\n` + - `* Very cool.\n` + - ` So, so cool.\n` + - `\n` + - `Goodbye!`); - - quickSnapshot( - 'inline images', - `<img src="snooping.png"> as USUAL...\n` + - `What do you know? <img src="cowabunga.png" width="24" height="32">\n` + - `[[album:cool-album|I'm on the left.]]<img src="im-on-the-right.jpg">\n` + - `<img src="im-on-the-left.jpg">[[album:cool-album|I'm on the right.]]\n` + - `Media time! <img src="media/misc/interesting.png"> Oh yeah!\n` + - `<img src="must.png"><img src="stick.png"><img src="together.png">\n` + - `And... all done! <img src="end-of-source.png">`); - - quickSnapshot( - 'non-inline image #1', - `<img src="spark.png">`); - - quickSnapshot( - 'non-inline image #2', - `Rad.\n` + - `<img src="spark.png">`); - - quickSnapshot( - 'non-inline image #3', - `<img src="spark.png">\n` + - `Baller.`); - - quickSnapshot( - 'dates', - `[[date:2023-04-13]] Yep!\n` + - `Very nice: [[date:25 October 2413]]`); - - quickSnapshot( - 'super basic string', - `Neat listing: [[string:listingPage.listAlbums.byDate.title]]`); - - quickSnapshot( - 'basic markdown', - `Hello *world!* This is **SO COOL.**`); - - quickSnapshot( - 'escape entire tag', - `\\[[album:cool-album|spooky]] [[album:cool-album|scary]]`); - - quickSnapshot( - 'escape end of tag', - `My favorite album is [[album:cool-album|[Tactical Omission\\]]].\n` + - `Your favorite album is [[album:cool-album|[Tactical Wha-Huh-Now]]].`); - - quickSnapshot( - 'escape markdown', - `What will it be, *ye fool?* \\*arr*`); - - quickSnapshot( - 'lyrics - basic line breaks', - `Hey, ho\n` + - `And away we go\n` + - `Truly, music\n` + - `\n` + - `(Oh yeah)\n` + - `(That's right)`, - {mode: 'lyrics'}); - - quickSnapshot( - 'lyrics - repeated and edge line breaks', - `\n\nWell, you know\nHow it goes\n\n\nYessiree\n\n\n`, - {mode: 'lyrics'}); - - quickSnapshot( - 'lyrics - line breaks around tags', - `The date be [[date:13 April 2004]]\n` + - `I say, the date be [[date:13 April 2004]]\n` + - `[[date:13 April 2004]]\n` + - `[[date:13 April 2004]][[date:13 April 2004]][[date:13 April 2004]]\n` + - `(Aye!)\n` + - `\n` + - `[[date:13 April 2004]]\n` + - `[[date:13 April 2004]][[date:13 April 2004]]\n` + - `[[date:13 April 2004]]\n` + - `\n` + - `[[date:13 April 2004]]\n` + - `[[date:13 April 2004]], and don't ye forget it`, - {mode: 'lyrics'}); - - quickSnapshot( - 'emails', - `Email cute dogs to qznebula@protonmail.com please.\n` + - `Just kidding... [unless?](mailto:qznebula@protonmail.com)`); - - quickSnapshot( - `absorb punctuation`, - `Don't you think this is an [interesting pull request](https://github.com/hsmusic/hsmusic-wiki/pull/567), Steve?\n` + - `Aren't you [interested](https://github.com/hsmusic/hsmusic-wiki/pull/567)... in [checking it out](https://github.com/hsmusic/hsmusic-wiki/pull/567)?!!`); - - // TODO: Snapshots for mode: inline - // TODO: Snapshots for mode: single-link -}); diff --git a/test/unit/content/dependencies/linkContribution.js b/test/unit/content/dependencies/linkContribution.js index 3ffd71d2..1baa80f8 100644 --- a/test/unit/content/dependencies/linkContribution.js +++ b/test/unit/content/dependencies/linkContribution.js @@ -1,7 +1,7 @@ import t from 'tap'; import {testContentFunctions} from '#test-lib'; -t.test('generateContributionLinks (unit)', async t => { +t.test('linkContribution (unit)', async t => { const artist1 = { name: 'Clark Powell', directory: 'clark-powell', @@ -24,7 +24,15 @@ t.test('generateContributionLinks (unit)', async t => { const annotation2 = 'Snooping'; const annotation3 = 'Arrangement'; - await testContentFunctions(t, 'generateContributionLinks (unit 1)', async (t, evaluate) => { + const thing1 = {}; + const thing2 = {}; + const thing3 = {}; + + const contribution1 = {artist: artist1, annotation: annotation1, thing: thing1}; + const contribution2 = {artist: artist2, annotation: annotation2, thing: thing2}; + const contribution3 = {artist: artist3, annotation: annotation3, thing: thing3}; + + await testContentFunctions(t, 'linkContribution (unit 1)', async (t, evaluate) => { const slots = { showAnnotation: true, showExternalLinks: true, @@ -71,15 +79,15 @@ t.test('generateContributionLinks (unit)', async t => { evaluate({ name: 'linkContribution', multiple: [ - {args: [{artist: artist1, annotation: annotation1}]}, - {args: [{artist: artist2, annotation: annotation2}]}, - {args: [{artist: artist3, annotation: annotation3}]}, + {args: [contribution1]}, + {args: [contribution2]}, + {args: [contribution3]}, ], slots, }); }); - await testContentFunctions(t, 'generateContributionLinks (unit 2)', async (t, evaluate) => { + await testContentFunctions(t, 'linkContribution (unit 2)', async (t, evaluate) => { const slots = { showAnnotation: false, showExternalLinks: false, @@ -127,9 +135,9 @@ t.test('generateContributionLinks (unit)', async t => { evaluate({ name: 'linkContribution', multiple: [ - {args: [{artist: artist1, annotation: annotation1}]}, - {args: [{artist: artist2, annotation: annotation2}]}, - {args: [{artist: artist3, annotation: annotation3}]}, + {args: [contribution1]}, + {args: [contribution2]}, + {args: [contribution3]}, ], slots, }); diff --git a/test/unit/data/cacheable-object.js b/test/unit/data/cacheable-object.js index 4b927248..4be31788 100644 --- a/test/unit/data/cacheable-object.js +++ b/test/unit/data/cacheable-object.js @@ -2,10 +2,14 @@ import t from 'tap'; import CacheableObject from '#cacheable-object'; -function newCacheableObject(PD) { - return new (class extends CacheableObject { - static [CacheableObject.propertyDescriptors] = PD; - }); +function newCacheableObject(propertyDescriptors) { + const constructor = class extends CacheableObject { + static [CacheableObject.propertyDescriptors] = propertyDescriptors; + }; + + constructor.finalizeCacheableObjectPrototype(); + + return Reflect.construct(constructor, []); } t.test(`CacheableObject simple separate update & expose`, t => { diff --git a/test/unit/data/composite/data/withPropertyFromObject.js b/test/unit/data/composite/data/withPropertyFromObject.js index 912c924c..068932e2 100644 --- a/test/unit/data/composite/data/withPropertyFromObject.js +++ b/test/unit/data/composite/data/withPropertyFromObject.js @@ -61,7 +61,7 @@ t.test(`withPropertyFromObject: "internal" input`, t => { ], }); - const thing = new (class extends CacheableObject { + const constructor = class extends CacheableObject { static [CacheableObject.propertyDescriptors] = { foo: { flags: {update: true, expose: false}, @@ -78,7 +78,11 @@ t.test(`withPropertyFromObject: "internal" input`, t => { }, }, }; - }); + }; + + constructor.finalizeCacheableObjectPrototype(); + + const thing = Reflect.construct(constructor, []); thing.foo = 100; thing.bar = 200; diff --git a/test/unit/data/composite/things/track/withAlbum.js b/test/unit/data/composite/things/track/withAlbum.js deleted file mode 100644 index 6f50776b..00000000 --- a/test/unit/data/composite/things/track/withAlbum.js +++ /dev/null @@ -1,119 +0,0 @@ -import t from 'tap'; - -import '#import-heck'; - -import Thing from '#thing'; - -import {compositeFrom, input} from '#composite'; -import {exposeConstant, exposeDependency} from '#composite/control-flow'; -import {withAlbum} from '#composite/things/track'; - -t.test(`withAlbum: basic behavior`, t => { - t.plan(3); - - const composite = compositeFrom({ - compose: false, - steps: [ - withAlbum(), - exposeDependency({dependency: '#album'}), - ], - }); - - t.match(composite, { - expose: { - dependencies: ['albumData', 'this'], - }, - }); - - const fakeTrack1 = { - [Thing.isThing]: true, - directory: 'foo', - }; - - const fakeTrack2 = { - [Thing.isThing]: true, - directory: 'bar', - }; - - const fakeAlbum = { - [Thing.isThing]: true, - directory: 'baz', - tracks: [fakeTrack1], - }; - - t.equal( - composite.expose.compute({ - albumData: [fakeAlbum], - this: fakeTrack1, - }), - fakeAlbum); - - t.equal( - composite.expose.compute({ - albumData: [fakeAlbum], - this: fakeTrack2, - }), - null); -}); - -t.test(`withAlbum: early exit conditions`, t => { - t.plan(4); - - const composite = compositeFrom({ - compose: false, - steps: [ - withAlbum(), - exposeConstant({ - value: input.value('bimbam'), - }), - ], - }); - - const fakeTrack1 = { - [Thing.isThing]: true, - directory: 'foo', - }; - - const fakeTrack2 = { - [Thing.isThing]: true, - directory: 'bar', - }; - - const fakeAlbum = { - [Thing.isThing]: true, - directory: 'baz', - tracks: [fakeTrack1], - }; - - t.equal( - composite.expose.compute({ - albumData: [fakeAlbum], - this: fakeTrack1, - }), - 'bimbam', - `does not early exit if albumData is present and contains the track`); - - t.equal( - composite.expose.compute({ - albumData: [fakeAlbum], - this: fakeTrack2, - }), - 'bimbam', - `does not early exit if albumData is present and does not contain the track`); - - t.equal( - composite.expose.compute({ - albumData: [], - this: fakeTrack1, - }), - 'bimbam', - `does not early exit if albumData is empty array`); - - t.equal( - composite.expose.compute({ - albumData: null, - this: fakeTrack1, - }), - null, - `early exits if albumData is null`); -}); diff --git a/test/unit/data/composite/wiki-data/withParsedCommentaryEntries.js b/test/unit/data/composite/wiki-data/withParsedCommentaryEntries.js deleted file mode 100644 index 411fd11d..00000000 --- a/test/unit/data/composite/wiki-data/withParsedCommentaryEntries.js +++ /dev/null @@ -1,373 +0,0 @@ -import t from 'tap'; - -import {compositeFrom, input} from '#composite'; -import thingConstructors from '#things'; - -import {exposeDependency} from '#composite/control-flow'; -import {withParsedCommentaryEntries} from '#composite/wiki-data'; - -const {Artist} = thingConstructors; - -const composite = compositeFrom({ - compose: false, - - steps: [ - withParsedCommentaryEntries({ - from: 'from', - }), - - exposeDependency({dependency: '#parsedCommentaryEntries'}), - ], -}); - -function stubArtist(artistName = `Test Artist`) { - const artist = new Artist(); - artist.name = artistName; - - return artist; -} - -t.test(`withParsedCommentaryEntries: basic behavior`, t => { - t.plan(7); - - const artist1 = stubArtist(`Mobius Trip`); - const artist2 = stubArtist(`Hadron Kaleido`); - const artist3 = stubArtist('Homestuck'); - - const artistData = [artist1, artist2, artist3]; - - t.match(composite, { - expose: { - dependencies: ['from', 'artistData'], - }, - }); - - t.same(composite.expose.compute({ - artistData, - from: - `<i>Mobius Trip:</i>\n` + - `Some commentary.\n` + - `Very cool.\n`, - }), [ - { - artists: [artist1], - artistDisplayText: null, - annotation: null, - date: null, - accessDate: null, - accessKind: null, - secondDate: null, - dateKind: null, - body: `Some commentary.\nVery cool.`, - }, - ]); - - t.same(composite.expose.compute({ - artistData, - from: - `<i>Mobius Trip|Moo-bius Trip:</i> (music, art, 12 January 2015)\n` + - `First commentary entry.\n` + - `Very cool.\n` + - `<i>Hadron Kaleido|<b>[[artist:hadron-kaleido|The Ol' Hadron]]</b>:</i> (moral support, 4/4/2022)\n` + - `Second commentary entry. Yes. So cool.\n` + - `<i>Mystery Artist:</i> (pingas, August 25, 2023)\n` + - `Oh no.. Oh dear...\n` + - `<i>Mobius Trip, Hadron Kaleido:</i>\n` + - `And back around we go.`, - }), [ - { - artists: [artist1], - artistDisplayText: `Moo-bius Trip`, - annotation: `music, art`, - date: new Date('12 January 2015'), - body: `First commentary entry.\nVery cool.`, - secondDate: null, - dateKind: null, - accessDate: null, - accessKind: null, - }, - { - artists: [artist2], - artistDisplayText: `<b>[[artist:hadron-kaleido|The Ol' Hadron]]</b>`, - annotation: `moral support`, - date: new Date('4 April 2022'), - body: `Second commentary entry. Yes. So cool.`, - secondDate: null, - dateKind: null, - accessDate: null, - accessKind: null, - }, - { - artists: [], - artistDisplayText: null, - annotation: `pingas`, - date: new Date('25 August 2023'), - body: `Oh no.. Oh dear...`, - secondDate: null, - dateKind: null, - accessDate: null, - accessKind: null, - }, - { - artists: [artist1, artist2], - artistDisplayText: null, - annotation: null, - date: null, - body: `And back around we go.`, - secondDate: null, - dateKind: null, - accessDate: null, - accessKind: null, - }, - ]); - - t.same(composite.expose.compute({ - artistData, - from: - `<i>Homestuck:</i> ([Bandcamp credits blurb](https://web.archive.org/web/20201024170202/https://homestuck.bandcamp.com/track/sburban-countdown-3) on "Homestuck Vol. 1-4 (with Midnight Crew: Drawing Dead)", 10/25/2019)\n` + - `\n` + - `Written by [[artist:michael-guy-bowman|Michael Guy Bowman]]<br>\n` + - `Arrangement by [[artist:mark-j-hadley|Mark Hadley]]\n` + - `\n` + - `<i>Homestuck:</i> ([fake](https://web.archive.org/web/20201024170202/https://homestuck.bandcamp.com/fake), 7/20/2019 captured 4/13/2024)\n` + - `This isn't real!\n` + - `\n` + - `<i>Homestuck:</i> ([fake](https://homestuck.com/fake), 10/25/2011 accessed 10/27/2011)\n` + - `This isn't real either!\n` + - `\n` + - `<i>Homestuck:</i> ([fake](https://web.archive.org/web/20201024170202/https://homestuck.bandcamp.com/fake), 7/20/2019 accessed 4/13/2024)\n` + - `Not this one, neither!\n` - }), [ - { - artists: [artist3], - artistDisplayText: null, - annotation: `[Bandcamp credits blurb](https://web.archive.org/web/20201024170202/https://homestuck.bandcamp.com/track/sburban-countdown-3) on "Homestuck Vol. 1-4 (with Midnight Crew: Drawing Dead)"`, - date: new Date('10/25/2019'), - body: - `Written by [[artist:michael-guy-bowman|Michael Guy Bowman]]<br>\n` + - `Arrangement by [[artist:mark-j-hadley|Mark Hadley]]`, - secondDate: null, - dateKind: null, - accessDate: new Date('10/24/2020'), - accessKind: 'captured', - }, - { - artists: [artist3], - artistDisplayText: null, - annotation: `[fake](https://web.archive.org/web/20201024170202/https://homestuck.bandcamp.com/fake)`, - date: new Date('7/20/2019'), - body: `This isn't real!`, - secondDate: null, - dateKind: null, - accessDate: new Date('4/13/2024'), - accessKind: 'captured', - }, - { - artists: [artist3], - artistDisplayText: null, - annotation: `[fake](https://homestuck.com/fake)`, - date: new Date('10/25/2011'), - body: `This isn't real either!`, - secondDate: null, - dateKind: null, - accessDate: new Date('10/27/2011'), - accessKind: 'accessed', - }, - { - artists: [artist3], - artistDisplayText: null, - annotation: `[fake](https://web.archive.org/web/20201024170202/https://homestuck.bandcamp.com/fake)`, - date: new Date('7/20/2019'), - body: `Not this one, neither!`, - secondDate: null, - dateKind: null, - accessDate: new Date('4/13/2024'), - accessKind: 'accessed', - }, - ]); - - t.same(composite.expose.compute({ - artistData, - from: - `<i>Homestuck:</i> ([MSPA sound credits](https://web.archive.org/web/20120805031705/http://www.mspaintadventures.com:80/soundcredits.html), sometime 6/21/2012 - 8/5/2012)\n` + - `\n` + - `[[flash:246|Page 2146]] - <b>"Sburban Countdown"</b><br>\n` + - `Available on Bandcamp in [[album:homestuck-vol-1-4|Homestuck Vol. 1-4]]<br>\n` + - `Written by [[artist:michael-guy-bowman|Michael Guy Bowman]]<br>\n` + - `Arrangement by [[artist:mark-j-hadley|Mark Hadley]]\n` + - `\n` + - `<i>Homestuck:</i> ([fake](https://web.archive.org/web/20201024170202/https://homestuck.bandcamp.com/fake), 7/20/2019 - 7/20/2022 captured 4/13/2024)\n` + - `It's goin' once.\n` + - `\n` + - `<i>Homestuck:</i> (10/25/2011 - 10/28/2011 accessed 10/27/2011)\n` + - `It's goin' twice.\n` + - `\n` + - `<i>Homestuck:</i> ([fake](https://web.archive.org/web/20201024170202/https://homestuck.bandcamp.com/fake), 7/20/2019 - 7/20/2022 accessed 4/13/2024)\n` + - `It's goin' thrice!\n` - }), [ - { - artists: [artist3], - artistDisplayText: null, - annotation: `[MSPA sound credits](https://web.archive.org/web/20120805031705/http://www.mspaintadventures.com:80/soundcredits.html)`, - body: - `[[flash:246|Page 2146]] - <b>"Sburban Countdown"</b><br>\n` + - `Available on Bandcamp in [[album:homestuck-vol-1-4|Homestuck Vol. 1-4]]<br>\n` + - `Written by [[artist:michael-guy-bowman|Michael Guy Bowman]]<br>\n` + - `Arrangement by [[artist:mark-j-hadley|Mark Hadley]]`, - date: new Date('6/21/2012'), - secondDate: new Date('8/5/2012'), - dateKind: 'sometime', - accessDate: new Date('8/5/2012'), - accessKind: 'captured', - }, - { - artists: [artist3], - artistDisplayText: null, - annotation: `[fake](https://web.archive.org/web/20201024170202/https://homestuck.bandcamp.com/fake)`, - body: `It's goin' once.`, - date: new Date('7/20/2019'), - secondDate: new Date('7/20/2022'), - dateKind: null, - accessDate: new Date('4/13/2024'), - accessKind: 'captured', - }, - { - artists: [artist3], - artistDisplayText: null, - annotation: '', // TODO: This should be null, but the regex isn't structured for that, at the moment. - body: `It's goin' twice.`, - date: new Date('10/25/2011'), - secondDate: new Date('10/28/2011'), - dateKind: null, - accessDate: new Date('10/27/2011'), - accessKind: 'accessed', - - }, - { - artists: [artist3], - artistDisplayText: null, - annotation: `[fake](https://web.archive.org/web/20201024170202/https://homestuck.bandcamp.com/fake)`, - body: `It's goin' thrice!`, - date: new Date('7/20/2019'), - secondDate: new Date('7/20/2022'), - dateKind: null, - accessDate: new Date('4/13/2024'), - accessKind: 'accessed', - }, - ]); - - t.same(composite.expose.compute({ - artistData, - from: - `<i>Homestuck:</i> ([MSPA sound credits](https://web.archive.org/web/20120805031705/http://www.mspaintadventures.com:80/soundcredits.html), sometime 6/21/2012 - 8/5/2012)\n` + - `\n` + - `[[flash:246|Page 2146]] - <b>"Sburban Countdown"</b><br>\n` + - `Available on Bandcamp in [[album:homestuck-vol-1-4|Homestuck Vol. 1-4]]<br>\n` + - `Written by [[artist:michael-guy-bowman|Michael Guy Bowman]]<br>\n` + - `Arrangement by [[artist:mark-j-hadley|Mark Hadley]]\n` + - `\n` + - `<i>Homestuck:</i> ([fake](https://web.archive.org/web/20201024170202/https://homestuck.bandcamp.com/fake), 7/20/2019 - 7/20/2022 captured 4/13/2024)\n` + - `It's goin' once.\n` + - `\n` + - `<i>Homestuck:</i> (10/25/2011 - 10/28/2011 accessed 10/27/2011)\n` + - `It's goin' twice.\n` + - `\n` + - `<i>Homestuck:</i> ([fake](https://web.archive.org/web/20201024170202/https://homestuck.bandcamp.com/fake), 7/20/2019 - 7/20/2022 accessed 4/13/2024)\n` + - `It's goin' thrice!\n` - }), [ - { - artists: [artist3], - artistDisplayText: null, - annotation: `[MSPA sound credits](https://web.archive.org/web/20120805031705/http://www.mspaintadventures.com:80/soundcredits.html)`, - body: - `[[flash:246|Page 2146]] - <b>"Sburban Countdown"</b><br>\n` + - `Available on Bandcamp in [[album:homestuck-vol-1-4|Homestuck Vol. 1-4]]<br>\n` + - `Written by [[artist:michael-guy-bowman|Michael Guy Bowman]]<br>\n` + - `Arrangement by [[artist:mark-j-hadley|Mark Hadley]]`, - date: new Date('6/21/2012'), - secondDate: new Date('8/5/2012'), - dateKind: 'sometime', - accessDate: new Date('8/5/2012'), - accessKind: 'captured', - }, - { - artists: [artist3], - artistDisplayText: null, - annotation: `[fake](https://web.archive.org/web/20201024170202/https://homestuck.bandcamp.com/fake)`, - body: `It's goin' once.`, - date: new Date('7/20/2019'), - secondDate: new Date('7/20/2022'), - dateKind: null, - accessDate: new Date('4/13/2024'), - accessKind: 'captured', - }, - { - artists: [artist3], - artistDisplayText: null, - annotation: '', // TODO: This should be null, but the regex isn't structured for that, at the moment. - body: `It's goin' twice.`, - date: new Date('10/25/2011'), - secondDate: new Date('10/28/2011'), - dateKind: null, - accessDate: new Date('10/27/2011'), - accessKind: 'accessed', - - }, - { - artists: [artist3], - artistDisplayText: null, - annotation: `[fake](https://web.archive.org/web/20201024170202/https://homestuck.bandcamp.com/fake)`, - body: `It's goin' thrice!`, - date: new Date('7/20/2019'), - secondDate: new Date('7/20/2022'), - dateKind: null, - accessDate: new Date('4/13/2024'), - accessKind: 'accessed', - }, - ]); - - t.same(composite.expose.compute({ - artistData, - from: - `<i>Homestuck:</i> ([Homestuck sound credits](https://web.archive.org/web/20180717171235/https://www.homestuck.com/credits/sound), excerpt, around 4/3/2018)\n` + - `blablabla\n` + - `<i>Homestuck:</i> ([fake](https://web.archive.org/web/20201024170202/https://homestuck.bandcamp.com/fake), around 7/20/2019 - 7/20/2022 captured 4/13/2024)\n` + - `Snoopin', snoopin', snoo,\n` + - `<i>Homestuck:</i> ([fake](https://web.archive.org/web/20201024170202/https://homestuck.bandcamp.com/fake), throughout 7/20/2019 - 7/20/2022 accessed 4/13/2024)\n` + - `~ pingas ~\n` - }), [ - { - artists: [artist3], - artistDisplayText: null, - annotation: `[Homestuck sound credits](https://web.archive.org/web/20180717171235/https://www.homestuck.com/credits/sound), excerpt`, - body: `blablabla`, - date: new Date('4/3/2018'), - secondDate: null, - dateKind: 'around', - accessDate: new Date('7/17/2018'), - accessKind: 'captured', - }, - { - artists: [artist3], - artistDisplayText: null, - annotation: `[fake](https://web.archive.org/web/20201024170202/https://homestuck.bandcamp.com/fake)`, - body: `Snoopin', snoopin', snoo,`, - date: new Date('7/20/2019'), - secondDate: new Date('7/20/2022'), - dateKind: 'around', - accessDate: new Date('4/13/2024'), - accessKind: 'captured', - }, - { - artists: [artist3], - artistDisplayText: null, - annotation: `[fake](https://web.archive.org/web/20201024170202/https://homestuck.bandcamp.com/fake)`, - body: `~ pingas ~`, - date: new Date('7/20/2019'), - secondDate: new Date('7/20/2022'), - dateKind: 'throughout', - accessDate: new Date('4/13/2024'), - accessKind: 'accessed', - }, - ]); -}); diff --git a/test/unit/data/things/album.js b/test/unit/data/things/album.js deleted file mode 100644 index a64488f7..00000000 --- a/test/unit/data/things/album.js +++ /dev/null @@ -1,469 +0,0 @@ -import t from 'tap'; - -import thingConstructors from '#things'; - -import { - linkAndBindWikiData, - stubArtistAndContribs, - stubThing, - stubWikiData, -} from '#test-lib'; - -t.test(`Album.artTags`, t => { - const {Album, ArtTag} = thingConstructors; - - t.plan(3); - - const wikiData = stubWikiData(); - - const {contribs} = stubArtistAndContribs(wikiData); - const album = stubThing(wikiData, Album); - const tag1 = stubThing(wikiData, ArtTag, {name: `Tag 1`}); - const tag2 = stubThing(wikiData, ArtTag, {name: `Tag 2`}); - - linkAndBindWikiData(wikiData); - - t.same(album.artTags, [], - `artTags #1: defaults to empty array`); - - album.artTags = [`Tag 1`, `Tag 2`]; - - t.same(album.artTags, [], - `artTags #2: is empty if album doesn't have cover artists`); - - album.coverArtistContribs = contribs; - - t.same(album.artTags, [tag1, tag2], - `artTags #3: resolves if album has cover artists`); -}); - -t.test(`Album.bannerDimensions`, t => { - const {Album} = thingConstructors; - - t.plan(4); - - const wikiData = stubWikiData(); - - const album = stubThing(wikiData, Album); - const {contribs, badContribs} = stubArtistAndContribs(wikiData); - - linkAndBindWikiData(wikiData); - - t.equal(album.bannerDimensions, null, - `Album.bannerDimensions #1: defaults to null`); - - album.bannerDimensions = [1200, 275]; - - t.equal(album.bannerDimensions, null, - `Album.bannerDimensions #2: is null if bannerArtistContribs empty`); - - album.bannerArtistContribs = badContribs; - - t.equal(album.bannerDimensions, null, - `Album.bannerDimensions #3: is null if bannerArtistContribs resolves empty`); - - album.bannerArtistContribs = contribs; - - t.same(album.bannerDimensions, [1200, 275], - `Album.bannerDimensions #4: is own value`); -}); - -t.test(`Album.bannerFileExtension`, t => { - const {Album} = thingConstructors; - - t.plan(5); - - const wikiData = stubWikiData(); - - const album = stubThing(wikiData, Album); - const {contribs, badContribs} = stubArtistAndContribs(wikiData); - - linkAndBindWikiData(wikiData); - - t.equal(album.bannerFileExtension, null, - `Album.bannerFileExtension #1: defaults to null`); - - album.bannerFileExtension = 'png'; - - t.equal(album.bannerFileExtension, null, - `Album.bannerFileExtension #2: is null if bannerArtistContribs empty`); - - album.bannerArtistContribs = badContribs; - - t.equal(album.bannerFileExtension, null, - `Album.bannerFileExtension #3: is null if bannerArtistContribs resolves empty`); - - album.bannerArtistContribs = contribs; - - t.equal(album.bannerFileExtension, 'png', - `Album.bannerFileExtension #4: is own value`); - - album.bannerFileExtension = null; - - t.equal(album.bannerFileExtension, 'jpg', - `Album.bannerFileExtension #5: defaults to jpg`); -}); - -t.test(`Album.bannerStyle`, t => { - const {Album} = thingConstructors; - - t.plan(4); - - const wikiData = stubWikiData(); - - const album = stubThing(wikiData, Album); - const {contribs, badContribs} = stubArtistAndContribs(wikiData); - - linkAndBindWikiData(wikiData); - - t.equal(album.bannerStyle, null, - `Album.bannerStyle #1: defaults to null`); - - album.bannerStyle = `opacity: 0.5`; - - t.equal(album.bannerStyle, null, - `Album.bannerStyle #2: is null if bannerArtistContribs empty`); - - album.bannerArtistContribs = badContribs; - - t.equal(album.bannerStyle, null, - `Album.bannerStyle #3: is null if bannerArtistContribs resolves empty`); - - album.bannerArtistContribs = contribs; - - t.equal(album.bannerStyle, `opacity: 0.5`, - `Album.bannerStyle #4: is own value`); -}); - -t.test(`Album.coverArtDate`, t => { - const {Album} = thingConstructors; - - t.plan(6); - - const wikiData = stubWikiData(); - - const album = stubThing(wikiData, Album); - const {contribs, badContribs} = stubArtistAndContribs(wikiData); - - linkAndBindWikiData(wikiData); - - t.equal(album.coverArtDate, null, - `Album.coverArtDate #1: defaults to null`); - - album.date = new Date('2012-10-25'); - - t.equal(album.coverArtDate, null, - `Album.coverArtDate #2: is null if coverArtistContribs empty (1/2)`); - - album.coverArtDate = new Date('2011-04-13'); - - t.equal(album.coverArtDate, null, - `Album.coverArtDate #3: is null if coverArtistContribs empty (2/2)`); - - album.coverArtistContribs = contribs; - - t.same(album.coverArtDate, new Date('2011-04-13'), - `Album.coverArtDate #4: is own value`); - - album.coverArtDate = null; - - t.same(album.coverArtDate, new Date(`2012-10-25`), - `Album.coverArtDate #5: inherits album release date`); - - album.coverArtistContribs = badContribs; - - t.equal(album.coverArtDate, null, - `Album.coverArtDate #6: is null if coverArtistContribs resolves empty`); -}); - -t.test(`Album.coverArtFileExtension`, t => { - const {Album} = thingConstructors; - - t.plan(5); - - const wikiData = stubWikiData(); - - const album = stubThing(wikiData, Album); - const {contribs, badContribs} = stubArtistAndContribs(wikiData); - - linkAndBindWikiData(wikiData); - - t.equal(album.coverArtFileExtension, null, - `Album.coverArtFileExtension #1: is null if coverArtistContribs empty (1/2)`); - - album.coverArtFileExtension = 'png'; - - t.equal(album.coverArtFileExtension, null, - `Album.coverArtFileExtension #2: is null if coverArtistContribs empty (2/2)`); - - album.coverArtFileExtension = null; - album.coverArtistContribs = contribs; - - t.equal(album.coverArtFileExtension, 'jpg', - `Album.coverArtFileExtension #3: defaults to jpg`); - - album.coverArtFileExtension = 'png'; - - t.equal(album.coverArtFileExtension, 'png', - `Album.coverArtFileExtension #4: is own value`); - - album.coverArtistContribs = badContribs; - - t.equal(album.coverArtFileExtension, null, - `Album.coverArtFileExtension #5: is null if coverArtistContribs resolves empty`); -}); - -t.test(`Album.tracks`, t => { - const {Album, Track, TrackSection} = thingConstructors; - - t.plan(4); - - let wikiData = stubWikiData(); - - const album = stubThing(wikiData, Album); - album.directory = 'foo'; - - const track1 = stubThing(wikiData, Track, {directory: 'track1'}); - const track2 = stubThing(wikiData, Track, {directory: 'track2'}); - const track3 = stubThing(wikiData, Track, {directory: 'track3'}); - const tracks = [track1, track2, track3]; - - const section1 = stubThing(wikiData, TrackSection, {unqualifiedDirectory: 'section1'}); - const section2 = stubThing(wikiData, TrackSection, {unqualifiedDirectory: 'section2'}); - const section3 = stubThing(wikiData, TrackSection, {unqualifiedDirectory: 'section3'}); - const section4 = stubThing(wikiData, TrackSection, {unqualifiedDirectory: 'section4'}); - const section5 = stubThing(wikiData, TrackSection, {unqualifiedDirectory: 'section5'}); - const section6 = stubThing(wikiData, TrackSection, {unqualifiedDirectory: 'section6'}); - const sections = [section1, section2, section3, section4, section5, section6]; - - wikiData = null; - - for (const track of tracks) { - track.albumData = [album]; - } - - for (const section of sections) { - section.albumData = [album]; - } - - t.same(album.tracks, [], - `Album.tracks #1: defaults to empty array`); - - section1.tracks = [track1, track2, track3]; - - album.trackSections = [section1]; - - t.same(album.tracks, [track1, track2, track3], - `Album.tracks #2: pulls tracks from one track section`); - - section1.tracks = [track1]; - section2.tracks = [track2, track3]; - - album.trackSections = [section1, section2]; - - t.same(album.tracks, [track1, track2, track3], - `Album.tracks #3: pulls tracks from multiple track sections`); - - section1.tracks = [track1]; - section2.tracks = []; - section3.tracks = [track2]; - section4.tracks = []; - section5.tracks = []; - section6.tracks = [track3]; - - album.trackSections = [section1, section2, section3, section4, section5, section6]; - - t.same(album.tracks, [track1, track2, track3], - `Album.tracks #4: skips empty track sections`); -}); - -t.test(`Album.trackSections`, t => { - const {Album, Track, TrackSection} = thingConstructors; - - t.plan(7); - - let wikiData = stubWikiData(); - - const album = stubThing(wikiData, Album); - - const track1 = stubThing(wikiData, Track, {directory: 'track1'}); - const track2 = stubThing(wikiData, Track, {directory: 'track2'}); - const track3 = stubThing(wikiData, Track, {directory: 'track3'}); - const track4 = stubThing(wikiData, Track, {directory: 'track4'}); - const tracks = [track1, track2, track3, track4]; - - const section1 = stubThing(wikiData, TrackSection, {unqualifiedDirectory: 'section1'}); - const section2 = stubThing(wikiData, TrackSection, {unqualifiedDirectory: 'section2'}); - const section3 = stubThing(wikiData, TrackSection, {unqualifiedDirectory: 'section3'}); - const section4 = stubThing(wikiData, TrackSection, {unqualifiedDirectory: 'section4'}); - const section5 = stubThing(wikiData, TrackSection, {unqualifiedDirectory: 'section5'}); - const sections = [section1, section2, section3, section4, section5]; - - wikiData = null; - - for (const track of tracks) { - track.albumData = [album]; - } - - for (const section of sections) { - section.albumData = [album]; - } - - section1.tracks = [track1, track2]; - section2.tracks = [track3, track4]; - - album.trackSections = [section1, section2]; - - t.match(album.trackSections, [ - {tracks: [track1, track2]}, - {tracks: [track3, track4]}, - ], `Album.trackSections #1: exposes tracks`); - - t.match(album.trackSections, [ - {tracks: [track1, track2], startIndex: 0}, - {tracks: [track3, track4], startIndex: 2}, - ], `Album.trackSections #2: exposes startIndex`); - - section1.tracks = [track1]; - section2.tracks = [track2]; - section3.tracks = [track3]; - - section1.name = 'First section'; - section2.name = 'Second section'; - - album.trackSections = [section1, section2, section3]; - - t.match(album.trackSections, [ - {name: 'First section', tracks: [track1]}, - {name: 'Second section', tracks: [track2]}, - {name: 'Unnamed Track Section', tracks: [track3]}, - ], `Album.trackSections #3: exposes name, with fallback value`); - - album.color = '#123456'; - - section2.color = '#abcdef'; - - // XXX_decacheWikiData - album.trackSections = []; - album.trackSections = [section1, section2, section3]; - - t.match(album.trackSections, [ - {tracks: [track1], color: '#123456'}, - {tracks: [track2], color: '#abcdef'}, - {tracks: [track3], color: '#123456'}, - ], `Album.trackSections #4: exposes color, inherited from album`); - - section2.dateOriginallyReleased = new Date('2009-04-11'); - - // XXX_decacheWikiData - album.trackSections = []; - album.trackSections = [section1, section2, section3]; - - t.match(album.trackSections, [ - {tracks: [track1], dateOriginallyReleased: null}, - {tracks: [track2], dateOriginallyReleased: new Date('2009-04-11')}, - {tracks: [track3], dateOriginallyReleased: null}, - ], `Album.trackSections #5: exposes dateOriginallyReleased, if present`); - - section1.isDefaultTrackSection = true; - section2.isDefaultTrackSection = false; - - // XXX_decacheWikiData - album.trackSections = []; - album.trackSections = [section1, section2, section3]; - - t.match(album.trackSections, [ - {tracks: [track1], isDefaultTrackSection: true}, - {tracks: [track2], isDefaultTrackSection: false}, - {tracks: [track3], isDefaultTrackSection: false}, - ], `Album.trackSections #6: exposes isDefaultTrackSection, defaults to false`); - - section1.tracks = [track1, track2]; - section2.tracks = [track3]; - section3.tracks = []; - section4.tracks = []; - section5.tracks = [track4]; - - section1.color = '#112233'; - section2.color = '#334455'; - section3.color = '#bbbbba'; - section4.color = '#556677'; - section5.color = '#778899'; - - album.trackSections = [section1, section2, section3, section4, section5]; - - t.match(album.trackSections, [ - {tracks: [track1, track2], color: '#112233'}, - {tracks: [track3], color: '#334455'}, - {tracks: [], color: '#bbbbba'}, - {tracks: [], color: '#556677'}, - {tracks: [track4], color: '#778899'}, - ], `Album.trackSections #7: keeps empty sections`); -}); - -t.test(`Album.wallpaperFileExtension`, t => { - const {Album} = thingConstructors; - - t.plan(5); - - const wikiData = stubWikiData(); - - const album = stubThing(wikiData, Album); - const {contribs, badContribs} = stubArtistAndContribs(wikiData); - - linkAndBindWikiData(wikiData); - - t.equal(album.wallpaperFileExtension, null, - `Album.wallpaperFileExtension #1: defaults to null`); - - album.wallpaperFileExtension = 'png'; - - t.equal(album.wallpaperFileExtension, null, - `Album.wallpaperFileExtension #2: is null if wallpaperArtistContribs empty`); - - album.wallpaperArtistContribs = contribs; - - t.equal(album.wallpaperFileExtension, 'png', - `Album.wallpaperFileExtension #3: is own value`); - - album.wallpaperFileExtension = null; - - t.equal(album.wallpaperFileExtension, 'jpg', - `Album.wallpaperFileExtension #4: defaults to jpg`); - - album.wallpaperArtistContribs = badContribs; - - t.equal(album.wallpaperFileExtension, null, - `Album.wallpaperFileExtension #5: is null if wallpaperArtistContribs resolves empty`); -}); - -t.test(`Album.wallpaperStyle`, t => { - const {Album} = thingConstructors; - - t.plan(4); - - const wikiData = stubWikiData(); - - const album = stubThing(wikiData, Album); - const {contribs, badContribs} = stubArtistAndContribs(wikiData); - - linkAndBindWikiData(wikiData); - - t.equal(album.wallpaperStyle, null, - `Album.wallpaperStyle #1: defaults to null`); - - album.wallpaperStyle = `opacity: 0.5`; - - t.equal(album.wallpaperStyle, null, - `Album.wallpaperStyle #2: is null if wallpaperArtistContribs empty`); - - album.wallpaperArtistContribs = badContribs; - - t.equal(album.wallpaperStyle, null, - `Album.wallpaperStyle #3: is null if wallpaperArtistContribs resolves empty`); - - album.wallpaperArtistContribs = contribs; - - t.equal(album.wallpaperStyle, `opacity: 0.5`, - `Album.wallpaperStyle #4: is own value`); -}); diff --git a/test/unit/data/things/art-tag.js b/test/unit/data/things/art-tag.js deleted file mode 100644 index 015acd0e..00000000 --- a/test/unit/data/things/art-tag.js +++ /dev/null @@ -1,26 +0,0 @@ -import t from 'tap'; - -import thingConstructors from '#things'; - -t.test(`ArtTag.nameShort`, t => { - const {ArtTag} = thingConstructors; - - t.plan(3); - - const artTag = new ArtTag(); - - artTag.name = `Dave Strider`; - - t.equal(artTag.nameShort, `Dave Strider`, - `ArtTag #1: defaults to name`); - - artTag.name = `Dave Strider (Homestuck)`; - - t.equal(artTag.nameShort, `Dave Strider`, - `ArtTag #2: trims parenthical part at end`); - - artTag.name = `This (And) That (Then)`; - - t.equal(artTag.nameShort, `This (And) That`, - `ArtTag #2: doesn't trim midlde parenthical part`); -}); diff --git a/test/unit/data/things/flash.js b/test/unit/data/things/flash.js deleted file mode 100644 index de39e80d..00000000 --- a/test/unit/data/things/flash.js +++ /dev/null @@ -1,40 +0,0 @@ -import t from 'tap'; - -import thingConstructors from '#things'; - -import { - linkAndBindWikiData, - stubThing, - stubWikiData, -} from '#test-lib'; - -t.test(`Flash.color`, t => { - const {Flash, FlashAct} = thingConstructors; - - t.plan(4); - - const wikiData = stubWikiData(); - - const flash = stubThing(wikiData, Flash, {directory: 'my-flash'}); - const flashAct = stubThing(wikiData, FlashAct, {flashes: ['flash:my-flash']}); - - const {XXX_decacheWikiData} = linkAndBindWikiData(wikiData); - - t.equal(flash.color, null, - `color #1: defaults to null`); - - flashAct.color = '#abcdef'; - XXX_decacheWikiData(); - - t.equal(flash.color, '#abcdef', - `color #2: inherits from flash act`); - - flash.color = '#123456'; - - t.equal(flash.color, '#123456', - `color #3: is own value`); - - t.throws(() => { flash.color = '#aeiouw'; }, - {cause: TypeError}, - `color #4: must be set to valid color`); -}); diff --git a/test/unit/data/things/track.js b/test/unit/data/things/track.js deleted file mode 100644 index 403dc064..00000000 --- a/test/unit/data/things/track.js +++ /dev/null @@ -1,752 +0,0 @@ -import t from 'tap'; - -import {bindFind} from '#find'; -import thingConstructors from '#things'; - -import { - linkAndBindWikiData, - stubArtistAndContribs, - stubFlashAndAct, - stubThing, - stubTrackAndAlbum, - stubWikiData, -} from '#test-lib'; - -t.test(`Track.album`, t => { - const {Album, Track, TrackSection} = thingConstructors; - - t.plan(6); - - // Note: These asserts use manual albumData/trackData relationships - // to illustrate more specifically the properties which are expected to - // be relevant for this case. Other properties use the same underlying - // get-album behavior as Track.album so aren't tested as aggressively. - - let wikiData = stubWikiData(); - - const track1 = stubThing(wikiData, Track, {directory: 'track1'}); - const track2 = stubThing(wikiData, Track, {directory: 'track2'}); - const album1 = stubThing(wikiData, Album); - const album2 = stubThing(wikiData, Album); - const section1 = stubThing(wikiData, TrackSection, {unqualifiedDirectory: 'section1'}); - const section2 = stubThing(wikiData, TrackSection, {unqualifiedDirectory: 'section2'}); - - wikiData = null; - - t.equal(track1.album, null, - `album #1: defaults to null`); - - track1.albumData = [album1, album2]; - track2.albumData = [album1, album2]; - section1.tracks = [track1]; - section2.tracks = [track2]; - section1.albumData = [album1]; - section2.albumData = [album2]; - album1.trackSections = [section1]; - album2.trackSections = [section2]; - - t.equal(track1.album, album1, - `album #2: is album when album's trackSections matches track`); - - track1.albumData = [album2, album1]; - - t.equal(track1.album, album1, - `album #3: is album when albumData is in different order`); - - track1.albumData = []; - - t.equal(track1.album, null, - `album #4: is null when track missing albumData`); - - section1.tracks = []; - - // XXX_decacheWikiData - album1.trackSections = []; - album1.trackSections = [section1]; - track1.albumData = []; - track1.albumData = [album2, album1]; - - t.equal(track1.album, null, - `album #5: is null when album track section missing tracks`); - - section1.tracks = [track2]; - - // XXX_decacheWikiData - album1.trackSections = []; - album1.trackSections = [section1]; - track1.albumData = []; - track1.albumData = [album2, album1]; - - t.equal(track1.album, null, - `album #6: is null when album track section doesn't match track`); -}); - -t.test(`Track.alwaysReferenceByDirectory`, t => { - t.plan(7); - - const wikiData = stubWikiData(); - - const {track: originalTrack} = - stubTrackAndAlbum(wikiData, 'original-track', 'original-album'); - - const {track: rereleaseTrack, album: rereleaseAlbum} = - stubTrackAndAlbum(wikiData, 'rerelease-track', 'rerelease-album'); - - originalTrack.name = 'Cowabunga'; - rereleaseTrack.name = 'Cowabunga'; - - originalTrack.dataSourceAlbum = 'album:original-album'; - rereleaseTrack.dataSourceAlbum = 'album:rerelease-album'; - - rereleaseTrack.originalReleaseTrack = 'track:original-track'; - - const {XXX_decacheWikiData} = linkAndBindWikiData(wikiData); - - t.equal(originalTrack.alwaysReferenceByDirectory, false, - `alwaysReferenceByDirectory #1: defaults to false`); - - t.equal(rereleaseTrack.alwaysReferenceByDirectory, true, - `alwaysReferenceByDirectory #2: is true if rerelease name matches original`); - - rereleaseTrack.name = 'Foo Dog!'; - - t.equal(rereleaseTrack.alwaysReferenceByDirectory, false, - `alwaysReferenceByDirectory #3: is false if rerelease name doesn't match original`); - - rereleaseTrack.name = `COWabunga`; - - t.equal(rereleaseTrack.alwaysReferenceByDirectory, false, - `alwaysReferenceByDirectory #4: is false if rerelease name doesn't match original exactly`); - - rereleaseAlbum.alwaysReferenceTracksByDirectory = true; - XXX_decacheWikiData(); - - t.equal(rereleaseTrack.alwaysReferenceByDirectory, true, - `alwaysReferenceByDirectory #5: is true if album's alwaysReferenceTracksByDirectory is true`); - - rereleaseTrack.alwaysReferenceByDirectory = false; - - t.equal(rereleaseTrack.alwaysReferenceByDirectory, false, - `alwaysReferenceByDirectory #6: doesn't inherit from album if set to false`); - - rereleaseTrack.name = 'Cowabunga'; - - t.equal(rereleaseTrack.alwaysReferenceByDirectory, false, - `alwaysReferenceByDirectory #7: doesn't compare original release name if set to false`); -}); - -t.test(`Track.artTags`, t => { - const {ArtTag} = thingConstructors; - - t.plan(6); - - const wikiData = stubWikiData(); - - const {track, album} = stubTrackAndAlbum(wikiData); - const {contribs} = stubArtistAndContribs(wikiData); - - const tag1 = - stubThing(wikiData, ArtTag, {name: `Tag 1`}); - - const tag2 = - stubThing(wikiData, ArtTag, {name: `Tag 2`}); - - const {XXX_decacheWikiData} = linkAndBindWikiData(wikiData); - - t.same(track.artTags, [], - `artTags #1: defaults to empty array`); - - track.artTags = [`Tag 1`, `Tag 2`]; - - t.same(track.artTags, [], - `artTags #2: is empty if track doesn't have cover artists`); - - track.coverArtistContribs = contribs; - - t.same(track.artTags, [tag1, tag2], - `artTags #3: resolves if track has cover artists`); - - track.coverArtistContribs = null; - album.trackCoverArtistContribs = contribs; - - XXX_decacheWikiData(); - - t.same(track.artTags, [tag1, tag2], - `artTags #4: resolves if track inherits cover artists`); - - track.disableUniqueCoverArt = true; - - t.same(track.artTags, [], - `artTags #5: is empty if track disables unique cover artwork`); - - album.coverArtistContribs = contribs; - album.artTags = [`Tag 2`]; - - XXX_decacheWikiData(); - - t.notSame(track.artTags, [tag2], - `artTags #6: doesn't inherit from album's art tags`); -}); - -t.test(`Track.artistContribs`, t => { - const {Album, Artist, Track, TrackSection} = thingConstructors; - - t.plan(4); - - const wikiData = stubWikiData(); - - const track = - stubThing(wikiData, Track); - - const section = - stubThing(wikiData, TrackSection, {tracks: [track]}); - - const album = - stubThing(wikiData, Album, {trackSections: [section]}); - - const artist1 = - stubThing(wikiData, Artist, {name: `Artist 1`}); - - const artist2 = - stubThing(wikiData, Artist, {name: `Artist 2`}); - - const {XXX_decacheWikiData} = linkAndBindWikiData(wikiData); - - t.same(track.artistContribs, [], - `artistContribs #1: defaults to empty array`); - - album.artistContribs = [ - {artist: `Artist 1`, annotation: `composition`}, - {artist: `Artist 2`, annotation: null}, - ]; - - XXX_decacheWikiData(); - - t.match(track.artistContribs, - [{artist: artist1, annotation: `composition`}, {artist: artist2, annotation: null}], - `artistContribs #2: inherits album artistContribs`); - - track.artistContribs = [ - {artist: `Artist 1`, annotation: `arrangement`}, - ]; - - t.match(track.artistContribs, [{artist: artist1, annotation: `arrangement`}], - `artistContribs #3: resolves from own value`); - - track.artistContribs = [ - {artist: `Artist 1`, annotation: `snooping`}, - {artist: `Artist 413`, annotation: `as`}, - {artist: `Artist 2`, annotation: `usual`}, - ]; - - t.match(track.artistContribs, - [{artist: artist1, annotation: `snooping`}, {artist: artist2, annotation: `usual`}], - `artistContribs #4: filters out names without matches`); -}); - -t.test(`Track.color`, t => { - t.plan(4); - - const wikiData = stubWikiData(); - - const {track, album, section} = stubTrackAndAlbum(wikiData); - - const {XXX_decacheWikiData} = linkAndBindWikiData(wikiData); - - t.equal(track.color, null, - `color #1: defaults to null`); - - album.color = '#abcdef'; - section.color = '#beeeef'; - - album.trackSections = [section]; - - XXX_decacheWikiData(); - - t.equal(track.color, '#beeeef', - `color #2: inherits from track section before album`); - - track.color = '#123456'; - - t.equal(track.color, '#123456', - `color #3: is own value`); - - t.throws(() => { track.color = '#aeiouw'; }, - {cause: TypeError}, - `color #4: must be set to valid color`); -}); - -t.test(`Track.commentatorArtists`, t => { - const {Artist, Track} = thingConstructors; - - t.plan(8); - - const wikiData = stubWikiData(); - - const track = stubThing(wikiData, Track); - const artist1 = stubThing(wikiData, Artist, {name: `SnooPING`}); - const artist2 = stubThing(wikiData, Artist, {name: `ASUsual`}); - const artist3 = stubThing(wikiData, Artist, {name: `Icy`}); - - linkAndBindWikiData(wikiData); - - // Keep track of the last commentary string in a separate value, since - // the track.commentary property exposes as a completely different format - // (i.e. an array of objects, one for each entry), and so isn't compatible - // with the += operator on its own. - let commentary; - - track.commentary = commentary = - `<i>SnooPING:</i>\n` + - `Wow.\n`; - - t.same(track.commentatorArtists, [artist1], - `Track.commentatorArtists #1: works with one commentator`); - - track.commentary = commentary += - `<i>ASUsual:</i>\n` + - `Yes!\n`; - - t.same(track.commentatorArtists, [artist1, artist2], - `Track.commentatorArtists #2: works with two commentators`); - - track.commentary = commentary += - `<i>Icy|<b>Icy annotation You Did There</b>:</i>\n` + - `Incredible.\n`; - - t.same(track.commentatorArtists, [artist1, artist2, artist3], - `Track.commentatorArtists #3: works with custom artist text`); - - track.commentary = commentary = - `<i>Icy:</i> (project manager)\n` + - `Very good track.\n`; - - t.same(track.commentatorArtists, [artist3], - `Track.commentatorArtists #4: works with annotation`); - - track.commentary = commentary = - `<i>Icy:</i> (project manager, 08/15/2023)\n` + - `Very very good track.\n`; - - t.same(track.commentatorArtists, [artist3], - `Track.commentatorArtists #5: works with date`); - - track.commentary = commentary += - `<i>Ohohohoho:</i>\n` + - `OHOHOHOHOHOHO...\n`; - - t.same(track.commentatorArtists, [artist3], - `Track.commentatorArtists #6: ignores artist names not found`); - - track.commentary = commentary += - `<i>Icy:</i>\n` + - `I'm back!\n`; - - t.same(track.commentatorArtists, [artist3], - `Track.commentatorArtists #7: ignores duplicate artist`); - - track.commentary = commentary += - `<i>SNooPING, ASUsual, Icy:</i>\n` + - `WITH ALL THREE POWERS COMBINED...`; - - t.same(track.commentatorArtists, [artist3, artist1, artist2], - `Track.commentatorArtists #8: works with more than one artist in one entry`); -}); - -t.test(`Track.coverArtistContribs`, t => { - const {Artist} = thingConstructors; - - t.plan(5); - - const wikiData = stubWikiData(); - - const {track, album} = stubTrackAndAlbum(wikiData); - const artist1 = stubThing(wikiData, Artist, {name: `Artist 1`}); - const artist2 = stubThing(wikiData, Artist, {name: `Artist 2`}); - - const {XXX_decacheWikiData} = linkAndBindWikiData(wikiData); - - t.same(track.coverArtistContribs, [], - `coverArtistContribs #1: defaults to empty array`); - - album.trackCoverArtistContribs = [ - {artist: `Artist 1`, annotation: `lines`}, - {artist: `Artist 2`, annotation: null}, - ]; - - XXX_decacheWikiData(); - - t.match(track.coverArtistContribs, - [{artist: artist1, annotation: `lines`}, {artist: artist2, annotation: null}], - `coverArtistContribs #2: inherits album trackCoverArtistContribs`); - - track.coverArtistContribs = [ - {artist: `Artist 1`, annotation: `collage`}, - ]; - - t.match(track.coverArtistContribs, [{artist: artist1, annotation: `collage`}], - `coverArtistContribs #3: resolves from own value`); - - track.coverArtistContribs = [ - {artist: `Artist 1`, annotation: `snooping`}, - {artist: `Artist 413`, annotation: `as`}, - {artist: `Artist 2`, annotation: `usual`}, - ]; - - t.match(track.coverArtistContribs, - [{artist: artist1, annotation: `snooping`}, {artist: artist2, annotation: `usual`}], - `coverArtistContribs #4: filters out names without matches`); - - track.disableUniqueCoverArt = true; - - t.same(track.coverArtistContribs, [], - `coverArtistContribs #5: is empty if track disables unique cover artwork`); -}); - -t.test(`Track.coverArtDate`, t => { - t.plan(8); - - const wikiData = stubWikiData(); - - const {track, album} = stubTrackAndAlbum(wikiData); - const {contribs, badContribs} = stubArtistAndContribs(wikiData); - - const {XXX_decacheWikiData} = linkAndBindWikiData(wikiData); - - track.coverArtistContribs = contribs; - - t.equal(track.coverArtDate, null, - `coverArtDate #1: defaults to null`); - - album.trackArtDate = new Date('2012-12-12'); - - XXX_decacheWikiData(); - - t.same(track.coverArtDate, new Date('2012-12-12'), - `coverArtDate #2: inherits album trackArtDate`); - - track.coverArtDate = new Date('2009-09-09'); - - t.same(track.coverArtDate, new Date('2009-09-09'), - `coverArtDate #3: is own value`); - - track.coverArtistContribs = []; - - t.equal(track.coverArtDate, null, - `coverArtDate #4: is null if track coverArtistContribs empty`); - - album.trackCoverArtistContribs = contribs; - - XXX_decacheWikiData(); - - t.same(track.coverArtDate, new Date('2009-09-09'), - `coverArtDate #5: is not null if album trackCoverArtistContribs specified`); - - album.trackCoverArtistContribs = badContribs; - - XXX_decacheWikiData(); - - t.equal(track.coverArtDate, null, - `coverArtDate #6: is null if album trackCoverArtistContribs resolves empty`); - - track.coverArtistContribs = badContribs; - - t.equal(track.coverArtDate, null, - `coverArtDate #7: is null if track coverArtistContribs resolves empty`); - - track.coverArtistContribs = contribs; - track.disableUniqueCoverArt = true; - - t.equal(track.coverArtDate, null, - `coverArtDate #8: is null if track disables unique cover artwork`); -}); - -t.test(`Track.coverArtFileExtension`, t => { - t.plan(8); - - const wikiData = stubWikiData(); - - const {track, album} = stubTrackAndAlbum(wikiData); - const {contribs} = stubArtistAndContribs(wikiData); - - const {XXX_decacheWikiData} = linkAndBindWikiData(wikiData); - - t.equal(track.coverArtFileExtension, null, - `coverArtFileExtension #1: defaults to null`); - - track.coverArtistContribs = contribs; - - t.equal(track.coverArtFileExtension, 'jpg', - `coverArtFileExtension #2: is jpg if has cover art and not further specified`); - - track.coverArtistContribs = []; - - album.coverArtistContribs = contribs; - XXX_decacheWikiData(); - - t.equal(track.coverArtFileExtension, null, - `coverArtFileExtension #3: only has value for unique cover art`); - - track.coverArtistContribs = contribs; - - album.trackCoverArtFileExtension = 'png'; - XXX_decacheWikiData(); - - t.equal(track.coverArtFileExtension, 'png', - `coverArtFileExtension #4: inherits album trackCoverArtFileExtension (1/2)`); - - track.coverArtFileExtension = 'gif'; - - t.equal(track.coverArtFileExtension, 'gif', - `coverArtFileExtension #5: is own value (1/2)`); - - track.coverArtistContribs = []; - - album.trackCoverArtistContribs = contribs; - XXX_decacheWikiData(); - - t.equal(track.coverArtFileExtension, 'gif', - `coverArtFileExtension #6: is own value (2/2)`); - - track.coverArtFileExtension = null; - - t.equal(track.coverArtFileExtension, 'png', - `coverArtFileExtension #7: inherits album trackCoverArtFileExtension (2/2)`); - - track.disableUniqueCoverArt = true; - - t.equal(track.coverArtFileExtension, null, - `coverArtFileExtension #8: is null if track disables unique cover art`); -}); - -t.test(`Track.date`, t => { - t.plan(3); - - const wikiData = stubWikiData(); - - const {track, album} = stubTrackAndAlbum(wikiData); - - const {XXX_decacheWikiData} = linkAndBindWikiData(wikiData); - - t.equal(track.date, null, - `date #1: defaults to null`); - - album.date = new Date('2012-12-12'); - XXX_decacheWikiData(); - - t.same(track.date, album.date, - `date #2: inherits from album`); - - track.dateFirstReleased = new Date('2009-09-09'); - - t.same(track.date, new Date('2009-09-09'), - `date #3: is own dateFirstReleased`); -}); - -t.test(`Track.featuredInFlashes`, t => { - t.plan(2); - - const wikiData = stubWikiData(); - - const {track} = stubTrackAndAlbum(wikiData, 'track1'); - const {flash: flash1} = stubFlashAndAct(wikiData, 'flash1'); - const {flash: flash2} = stubFlashAndAct(wikiData, 'flash2'); - - const {XXX_decacheWikiData} = linkAndBindWikiData(wikiData); - - t.same(track.featuredInFlashes, [], - `featuredInFlashes #1: defaults to empty array`); - - flash1.featuredTracks = ['track:track1']; - flash2.featuredTracks = ['track:track1']; - XXX_decacheWikiData(); - - t.same(track.featuredInFlashes, [flash1, flash2], - `featuredInFlashes #2: matches flashes' featuredTracks`); -}); - -t.test(`Track.hasUniqueCoverArt`, t => { - t.plan(7); - - const wikiData = stubWikiData(); - - const {track, album} = stubTrackAndAlbum(wikiData); - const {contribs, badContribs} = stubArtistAndContribs(wikiData); - - const {XXX_decacheWikiData} = linkAndBindWikiData(wikiData); - - t.equal(track.hasUniqueCoverArt, false, - `hasUniqueCoverArt #1: defaults to false`); - - album.trackCoverArtistContribs = contribs; - XXX_decacheWikiData(); - - t.equal(track.hasUniqueCoverArt, true, - `hasUniqueCoverArt #2: is true if album specifies trackCoverArtistContribs`); - - track.disableUniqueCoverArt = true; - - t.equal(track.hasUniqueCoverArt, false, - `hasUniqueCoverArt #3: is false if disableUniqueCoverArt is true (1/2)`); - - track.disableUniqueCoverArt = false; - - album.trackCoverArtistContribs = badContribs; - XXX_decacheWikiData(); - - t.equal(track.hasUniqueCoverArt, false, - `hasUniqueCoverArt #4: is false if album's trackCoverArtistContribs resolve empty`); - - track.coverArtistContribs = contribs; - - t.equal(track.hasUniqueCoverArt, true, - `hasUniqueCoverArt #5: is true if track specifies coverArtistContribs`); - - track.disableUniqueCoverArt = true; - - t.equal(track.hasUniqueCoverArt, false, - `hasUniqueCoverArt #6: is false if disableUniqueCoverArt is true (2/2)`); - - track.disableUniqueCoverArt = false; - - track.coverArtistContribs = badContribs; - - t.equal(track.hasUniqueCoverArt, false, - `hasUniqueCoverArt #7: is false if track's coverArtistContribs resolve empty`); -}); - -t.test(`Track.originalReleaseTrack`, t => { - t.plan(3); - - const wikiData = stubWikiData(); - - const {track: track1} = stubTrackAndAlbum(wikiData, 'track1'); - const {track: track2} = stubTrackAndAlbum(wikiData, 'track2'); - - linkAndBindWikiData(wikiData); - - t.equal(track2.originalReleaseTrack, null, - `originalReleaseTrack #1: defaults to null`); - - track2.originalReleaseTrack = 'track:track1'; - - t.equal(track2.originalReleaseTrack, track1, - `originalReleaseTrack #2: is resolved from own value`); - - track2.trackData = []; - - t.equal(track2.originalReleaseTrack, null, - `originalReleaseTrack #3: is null when track missing trackData`); -}); - -t.test(`Track.otherReleases`, t => { - t.plan(6); - - const wikiData = stubWikiData(); - - const {track: track1} = stubTrackAndAlbum(wikiData, 'track1'); - const {track: track2} = stubTrackAndAlbum(wikiData, 'track2'); - const {track: track3} = stubTrackAndAlbum(wikiData, 'track3'); - const {track: track4} = stubTrackAndAlbum(wikiData, 'track4'); - - const {linkWikiDataArrays, XXX_decacheWikiData} = linkAndBindWikiData(wikiData); - - t.same(track1.otherReleases, [], - `otherReleases #1: defaults to empty array`); - - track2.originalReleaseTrack = 'track:track1'; - track3.originalReleaseTrack = 'track:track1'; - track4.originalReleaseTrack = 'track:track1'; - XXX_decacheWikiData(); - - t.same(track1.otherReleases, [track2, track3, track4], - `otherReleases #2: otherReleases of original release are its rereleases`); - - wikiData.trackData = [track1, track3, track2, track4]; - linkWikiDataArrays({bindFind}); - - t.same(track1.otherReleases, [track3, track2, track4], - `otherReleases #3: otherReleases matches trackData order`); - - wikiData.trackData = [track3, track2, track1, track4]; - linkWikiDataArrays({bindFind}); - - t.same(track2.otherReleases, [track1, track3, track4], - `otherReleases #4: otherReleases of rerelease are original track then other rereleases (1/3)`); - - t.same(track3.otherReleases, [track1, track2, track4], - `otherReleases #5: otherReleases of rerelease are original track then other rereleases (2/3)`); - - t.same(track4.otherReleases, [track1, track3, track2], - `otherReleases #6: otherReleases of rerelease are original track then other rereleases (3/3)`); -}); - -t.test(`Track.referencedByTracks`, t => { - t.plan(4); - - const wikiData = stubWikiData(); - - const {track: track1} = stubTrackAndAlbum(wikiData, 'track1'); - const {track: track2} = stubTrackAndAlbum(wikiData, 'track2'); - const {track: track3} = stubTrackAndAlbum(wikiData, 'track3'); - const {track: track4} = stubTrackAndAlbum(wikiData, 'track4'); - - const {XXX_decacheWikiData} = linkAndBindWikiData(wikiData); - - t.same(track1.referencedByTracks, [], - `referencedByTracks #1: defaults to empty array`); - - track2.referencedTracks = ['track:track1']; - track3.referencedTracks = ['track:track1']; - XXX_decacheWikiData(); - - t.same(track1.referencedByTracks, [track2, track3], - `referencedByTracks #2: matches tracks' referencedTracks`); - - track4.sampledTracks = ['track:track1']; - XXX_decacheWikiData(); - - t.same(track1.referencedByTracks, [track2, track3], - `referencedByTracks #3: doesn't match tracks' sampledTracks`); - - track3.originalReleaseTrack = 'track:track2'; - XXX_decacheWikiData(); - - t.same(track1.referencedByTracks, [track2], - `referencedByTracks #4: doesn't include rereleases`); -}); - -t.test(`Track.sampledByTracks`, t => { - t.plan(4); - - const wikiData = stubWikiData(); - - const {track: track1} = stubTrackAndAlbum(wikiData, 'track1'); - const {track: track2} = stubTrackAndAlbum(wikiData, 'track2'); - const {track: track3} = stubTrackAndAlbum(wikiData, 'track3'); - const {track: track4} = stubTrackAndAlbum(wikiData, 'track4'); - - const {XXX_decacheWikiData} = linkAndBindWikiData(wikiData); - - t.same(track1.sampledByTracks, [], - `sampledByTracks #1: defaults to empty array`); - - track2.sampledTracks = ['track:track1']; - track3.sampledTracks = ['track:track1']; - XXX_decacheWikiData(); - - t.same(track1.sampledByTracks, [track2, track3], - `sampledByTracks #2: matches tracks' sampledTracks`); - - track4.referencedTracks = ['track:track1']; - XXX_decacheWikiData(); - - t.same(track1.sampledByTracks, [track2, track3], - `sampledByTracks #3: doesn't match tracks' referencedTracks`); - - track3.originalReleaseTrack = 'track:track2'; - XXX_decacheWikiData(); - - t.same(track1.sampledByTracks, [track2], - `sampledByTracks #4: doesn't include rereleases`); -}); diff --git a/test/unit/data/validators.js b/test/unit/data/validators.js index 3a217d6f..02f94866 100644 --- a/test/unit/data/validators.js +++ b/test/unit/data/validators.js @@ -339,7 +339,7 @@ t.test('isName', t => { t.plan(4); t.ok(isName('Dogz 2.0')); t.ok(isName('album:this-track-is-only-named-thusly-to-give-niklink-a-headache')); - t.throws(() => isName('')); + t.ok(() => isName('')); t.throws(() => isName(612)); }); @@ -374,8 +374,8 @@ test(t, 'validateReference', t => { t.ok(typeless('Hopes and Dreams')); t.ok(typeless('track:snowdin-town')); + t.ok(typeless('album:undertale-soundtrack')); t.throws(() => typeless(''), TypeError); - t.throws(() => typeless('album:undertale-soundtrack')); }); test(t, 'validateReferenceList', t => { diff --git a/test/unit/util/html.js b/test/unit/util/html.js index 1652aee2..126e36ff 100644 --- a/test/unit/util/html.js +++ b/test/unit/util/html.js @@ -545,7 +545,7 @@ t.test(`html.template`, t => { slots.slot1, slots.slot2, slots.slot3, - `(length: ${slots.slot4.length})`, + `(is null: ${slots.slot4 === null})`, ].join(' ')); }, }); @@ -557,7 +557,7 @@ t.test(`html.template`, t => { slot4: '', }); - t.equal(template3.toString(), `<span>123 0 false (length: 0)</span>`); + t.equal(template3.toString(), `<span>123 0 false (is null: true)</span>`); }); t.test(`Template - description errors`, t => { |