From ddc4a6aadc2f4b648614776bb488bbd24f781c05 Mon Sep 17 00:00:00 2001 From: Tobias Bocanegra Date: Wed, 10 Dec 2025 16:16:07 +0100 Subject: [PATCH 01/11] feat: preview from source bus --- package-lock.json | 2073 ++++++++++++++++++++++++++++++--- package.json | 1 + src/contentproxy/index.js | 8 +- src/contentproxy/sourcebus.js | 94 ++ 4 files changed, 1996 insertions(+), 180 deletions(-) create mode 100644 src/contentproxy/sourcebus.js diff --git a/package-lock.json b/package-lock.json index 55aa9c8..f54a182 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "@adobe/helix-admin-support": "5.0.3", "@adobe/helix-config": "5.7.0", "@adobe/helix-google-support": "4.0.1", + "@adobe/helix-html2md": "1.1.0", "@adobe/helix-mediahandler": "2.9.5", "@adobe/helix-onedrive-support": "12.1.4", "@adobe/helix-shared-body-data": "2.2.2", @@ -725,6 +726,592 @@ "node": "20 || >=22" } }, + "node_modules/@adobe/helix-html2md": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@adobe/helix-html2md/-/helix-html2md-1.1.0.tgz", + "integrity": "sha512-+by/8htM1P3deLpQw+7hLMBpPp2Bfncmiux9yQ7HsghAMH1wqj6HMvyERNFX3UcvVKP9i6xVNTkYqz8hVk5uIQ==", + "license": "Apache-2.0", + "dependencies": { + "@adobe/helix-markdown-support": "7.1.16", + "@adobe/helix-mediahandler": "2.9.5", + "@adobe/helix-shared-process-queue": "3.1.3", + "@adobe/helix-shared-string": "2.2.0", + "@adobe/micromark-extension-gridtables": "2.0.4", + "@adobe/remark-gridtables": "3.0.15", + "hast-util-select": "6.0.4", + "hast-util-to-mdast": "10.1.2", + "hast-util-to-string": "3.0.1", + "rehype-parse": "9.0.1", + "remark-gfm": "4.0.1", + "remark-stringify": "11.0.0", + "unified": "11.0.5", + "unist-util-visit": "5.0.0" + } + }, + "node_modules/@adobe/helix-html2md/node_modules/@adobe/helix-markdown-support": { + "version": "7.1.16", + "resolved": "https://registry.npmjs.org/@adobe/helix-markdown-support/-/helix-markdown-support-7.1.16.tgz", + "integrity": "sha512-Qpe1rP/RA3TKIuSgLkMJbFJuJmbSD+1WUsLT7JrlLo/N8mOlNM+ZIG8tbFpd3v5muVzjekt4ggRRLQlKdBmLjQ==", + "license": "Apache-2.0", + "dependencies": { + "hast-util-to-html": "9.0.5", + "js-yaml": "4.1.1", + "mdast-util-gfm-footnote": "2.1.0", + "mdast-util-gfm-strikethrough": "2.0.0", + "mdast-util-gfm-table": "2.0.0", + "mdast-util-gfm-task-list-item": "2.0.0", + "mdast-util-phrasing": "4.1.0", + "mdast-util-to-hast": "13.2.0", + "micromark-extension-gfm-footnote": "2.1.0", + "micromark-extension-gfm-strikethrough": "2.1.0", + "micromark-extension-gfm-table": "2.1.1", + "micromark-extension-gfm-tagfilter": "2.0.0", + "micromark-extension-gfm-task-list-item": "2.1.0", + "micromark-util-character": "2.1.1", + "micromark-util-combine-extensions": "2.0.1", + "micromark-util-symbol": "2.0.1", + "unist-util-find": "3.0.0", + "unist-util-visit": "5.0.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "unified": "11.x" + } + }, + "node_modules/@adobe/helix-html2md/node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@adobe/helix-html2md/node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/@adobe/helix-html2md/node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/@adobe/helix-html2md/node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/@adobe/helix-html2md/node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@adobe/helix-html2md/node_modules/longest-streak": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", + "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/@adobe/helix-html2md/node_modules/markdown-table": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz", + "integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/@adobe/helix-html2md/node_modules/mdast-util-find-and-replace": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz", + "integrity": "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "escape-string-regexp": "^5.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@adobe/helix-html2md/node_modules/mdast-util-from-markdown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz", + "integrity": "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark": "^4.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@adobe/helix-html2md/node_modules/mdast-util-gfm": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.1.0.tgz", + "integrity": "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==", + "license": "MIT", + "dependencies": { + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-gfm-autolink-literal": "^2.0.0", + "mdast-util-gfm-footnote": "^2.0.0", + "mdast-util-gfm-strikethrough": "^2.0.0", + "mdast-util-gfm-table": "^2.0.0", + "mdast-util-gfm-task-list-item": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@adobe/helix-html2md/node_modules/mdast-util-gfm-autolink-literal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz", + "integrity": "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "ccount": "^2.0.0", + "devlop": "^1.0.0", + "mdast-util-find-and-replace": "^3.0.0", + "micromark-util-character": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@adobe/helix-html2md/node_modules/mdast-util-gfm-strikethrough": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz", + "integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@adobe/helix-html2md/node_modules/mdast-util-gfm-table": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz", + "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "markdown-table": "^3.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@adobe/helix-html2md/node_modules/mdast-util-gfm-task-list-item": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz", + "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@adobe/helix-html2md/node_modules/mdast-util-to-markdown": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", + "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "longest-streak": "^3.0.0", + "mdast-util-phrasing": "^4.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@adobe/helix-html2md/node_modules/mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@adobe/helix-html2md/node_modules/micromark": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", + "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/@adobe/helix-html2md/node_modules/micromark-extension-gfm": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz", + "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==", + "license": "MIT", + "dependencies": { + "micromark-extension-gfm-autolink-literal": "^2.0.0", + "micromark-extension-gfm-footnote": "^2.0.0", + "micromark-extension-gfm-strikethrough": "^2.0.0", + "micromark-extension-gfm-table": "^2.0.0", + "micromark-extension-gfm-tagfilter": "^2.0.0", + "micromark-extension-gfm-task-list-item": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@adobe/helix-html2md/node_modules/micromark-extension-gfm-autolink-literal": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz", + "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==", + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@adobe/helix-html2md/node_modules/micromark-extension-gfm-strikethrough": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz", + "integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@adobe/helix-html2md/node_modules/micromark-extension-gfm-table": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz", + "integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@adobe/helix-html2md/node_modules/micromark-extension-gfm-tagfilter": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz", + "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==", + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@adobe/helix-html2md/node_modules/micromark-extension-gfm-task-list-item": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz", + "integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@adobe/helix-html2md/node_modules/remark-gfm": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz", + "integrity": "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-gfm": "^3.0.0", + "micromark-extension-gfm": "^3.0.0", + "remark-parse": "^11.0.0", + "remark-stringify": "^11.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@adobe/helix-html2md/node_modules/remark-parse": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", + "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@adobe/helix-html2md/node_modules/remark-stringify": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz", + "integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-to-markdown": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@adobe/helix-html2md/node_modules/trough": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/@adobe/helix-html2md/node_modules/unified": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "bail": "^2.0.0", + "devlop": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@adobe/helix-html2md/node_modules/unist-util-is": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", + "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@adobe/helix-html2md/node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@adobe/helix-html2md/node_modules/unist-util-visit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", + "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@adobe/helix-html2md/node_modules/unist-util-visit-parents": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz", + "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@adobe/helix-html2md/node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@adobe/helix-html2md/node_modules/vfile-message": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz", + "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@adobe/helix-html2md/node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/@adobe/helix-mediahandler": { "version": "2.9.5", "resolved": "https://registry.npmjs.org/@adobe/helix-mediahandler/-/helix-mediahandler-2.9.5.tgz", @@ -1081,29 +1668,280 @@ "@adobe/fetch": "^4.1.10" } }, - "node_modules/@adobe/helix-universal": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/@adobe/helix-universal/-/helix-universal-5.3.0.tgz", - "integrity": "sha512-1eKFpKZMNamJHhq6eFm9gMLhgQunsf34mEFbaqg9ChEXZYk18SYgUu5GeNTvzk5Rzo0h9AuSwLtnI2Up2OSiSA==", - "devOptional": true, - "license": "Apache-2.0", - "peer": true, + "node_modules/@adobe/helix-universal": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@adobe/helix-universal/-/helix-universal-5.3.0.tgz", + "integrity": "sha512-1eKFpKZMNamJHhq6eFm9gMLhgQunsf34mEFbaqg9ChEXZYk18SYgUu5GeNTvzk5Rzo0h9AuSwLtnI2Up2OSiSA==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@adobe/fetch": "4.2.3", + "aws4": "1.13.2" + } + }, + "node_modules/@adobe/helix-universal-devserver": { + "version": "1.1.143", + "resolved": "https://registry.npmjs.org/@adobe/helix-universal-devserver/-/helix-universal-devserver-1.1.143.tgz", + "integrity": "sha512-QMk3Bmn1VXegyv7rPl0Ba8wxtvw40nxFWKN6E40+f+h/iqLkyRrEoE2gaOoD05wjuoFnTIxh3pH9p6IS798B1Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@adobe/helix-deploy": "^13.0.0", + "@adobe/helix-universal": "^5.1.0", + "express": "5.1.0", + "fs-extra": "11.3.2" + } + }, + "node_modules/@adobe/mdast-util-gridtables": { + "version": "4.0.14", + "resolved": "https://registry.npmjs.org/@adobe/mdast-util-gridtables/-/mdast-util-gridtables-4.0.14.tgz", + "integrity": "sha512-qBXNrWsVyejpqjcZyK7Z0IxHQ2Rhed7DaoiGU9dIJtxVpq5BbTReIq9Nc+373HxJnB36qIO57YKoIop2MAqDwg==", + "license": "Apache-2.0", + "dependencies": { + "@adobe/micromark-extension-gridtables": "^2.0.4", + "mdast-util-from-markdown": "2.0.2", + "mdast-util-to-hast": "13.2.0", + "mdast-util-to-markdown": "2.1.2", + "unist-util-visit": "5.0.0" + } + }, + "node_modules/@adobe/mdast-util-gridtables/node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@adobe/mdast-util-gridtables/node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/@adobe/mdast-util-gridtables/node_modules/longest-streak": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", + "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/@adobe/mdast-util-gridtables/node_modules/mdast-util-from-markdown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz", + "integrity": "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark": "^4.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@adobe/mdast-util-gridtables/node_modules/mdast-util-to-markdown": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", + "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "longest-streak": "^3.0.0", + "mdast-util-phrasing": "^4.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@adobe/mdast-util-gridtables/node_modules/mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@adobe/mdast-util-gridtables/node_modules/micromark": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", + "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/@adobe/mdast-util-gridtables/node_modules/unist-util-is": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", + "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@adobe/mdast-util-gridtables/node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@adobe/mdast-util-gridtables/node_modules/unist-util-visit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", + "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@adobe/mdast-util-gridtables/node_modules/unist-util-visit-parents": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz", + "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@adobe/mdast-util-gridtables/node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/@adobe/micromark-extension-gridtables": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@adobe/micromark-extension-gridtables/-/micromark-extension-gridtables-2.0.4.tgz", + "integrity": "sha512-mzyRI2d0StuUv75VFnffYsHXRUo8J9RlK1EvjfmgHahwda1gNwe1Fqh4AYEZSWChf1HD/QBRcL6hEnxj9hedMg==", + "license": "Apache-2.0", + "dependencies": { + "micromark": "^4.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/@adobe/micromark-extension-gridtables/node_modules/micromark": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", + "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", "dependencies": { - "@adobe/fetch": "4.2.3", - "aws4": "1.13.2" + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" } }, - "node_modules/@adobe/helix-universal-devserver": { - "version": "1.1.143", - "resolved": "https://registry.npmjs.org/@adobe/helix-universal-devserver/-/helix-universal-devserver-1.1.143.tgz", - "integrity": "sha512-QMk3Bmn1VXegyv7rPl0Ba8wxtvw40nxFWKN6E40+f+h/iqLkyRrEoE2gaOoD05wjuoFnTIxh3pH9p6IS798B1Q==", - "dev": true, + "node_modules/@adobe/remark-gridtables": { + "version": "3.0.15", + "resolved": "https://registry.npmjs.org/@adobe/remark-gridtables/-/remark-gridtables-3.0.15.tgz", + "integrity": "sha512-WJkRv5Ws8c5jzCVj8tXn/Ib186uetP/IfwPknYUf0wsKpLfUP4a+SLVFSufKqZQIclETGxDITGbD/stIIhK/0A==", "license": "Apache-2.0", "dependencies": { - "@adobe/helix-deploy": "^13.0.0", - "@adobe/helix-universal": "^5.1.0", - "express": "5.1.0", - "fs-extra": "11.3.2" + "@adobe/mdast-util-gridtables": "4.0.14", + "@adobe/micromark-extension-gridtables": "2.0.4" } }, "node_modules/@adobe/semantic-release-coralogix": { @@ -2125,7 +2963,6 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.946.0.tgz", "integrity": "sha512-Y3ww3yd1wzmS2r3qgH3jg4MxCTdeNrae2J1BmdV+IW/2R2gFWJva5U5GbS6KUSUxanJBRG7gd8uOIi1b0EMOng==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@aws-crypto/sha1-browser": "5.2.0", "@aws-crypto/sha256-browser": "5.2.0", @@ -5714,7 +6551,6 @@ "integrity": "sha512-t54CUOsFMappY1Jbzb7fetWeO0n6K0k/4+/ZpkS+3Joz8I4VcvY9OiEBFRYISqaI2fq5sCiPtAjRDOzVYG8m+Q==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@octokit/auth-token": "^6.0.0", "@octokit/graphql": "^9.0.2", @@ -7349,6 +8185,15 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/debug": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, "node_modules/@types/hast": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", @@ -7389,6 +8234,12 @@ "@types/unist": "^2" } }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "license": "MIT" + }, "node_modules/@types/node": { "version": "24.7.2", "resolved": "https://registry.npmjs.org/@types/node/-/node-24.7.2.tgz", @@ -7471,7 +8322,6 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -7673,7 +8523,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, "license": "Python-2.0" }, "node_modules/argv-formatter": { @@ -9253,6 +10102,29 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/decode-named-character-reference": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.2.0.tgz", + "integrity": "sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==", + "license": "MIT", + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/decode-named-character-reference/node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/decompress-response": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", @@ -10009,7 +10881,6 @@ "integrity": "sha512-sjc7Y8cUD1IlwYcTS9qPSvGjAC8Ne9LctpxKKu3x/1IC9bnOg98Zy6GxEJUfr1NojMgVPlyANXYns8oE2c1TAA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -11789,6 +12660,20 @@ "node": ">= 0.4" } }, + "node_modules/hast-util-embedded": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-embedded/-/hast-util-embedded-3.0.0.tgz", + "integrity": "sha512-naH8sld4Pe2ep03qqULEtvYr7EjrLK2QHY8KJR6RJkTUjPGObe1vnx585uzem2hGra+s1q08DZZpfgDVYRbaXA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-is-element": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/hast-util-from-html": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/hast-util-from-html/-/hast-util-from-html-2.0.3.tgz", @@ -11946,6 +12831,19 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/hast-util-is-body-ok-link": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/hast-util-is-body-ok-link/-/hast-util-is-body-ok-link-3.0.1.tgz", + "integrity": "sha512-0qpnzOBLztXHbHQenVB8uNuxTnm/QBFUOmdOSsEn7GnBtyY07+ENTWVFBAnXd/zEgd9/SUG3lRY7hSIBWRgGpQ==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/hast-util-is-element": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-3.0.0.tgz", @@ -11959,6 +12857,42 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/hast-util-minify-whitespace": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hast-util-minify-whitespace/-/hast-util-minify-whitespace-1.0.1.tgz", + "integrity": "sha512-L96fPOVpnclQE0xzdWb/D12VT5FabA7SnZOUMtL1DbXmYiHJMXZvFkIZfiMmTCNJHUeO2K9UYNXoVyfz+QHuOw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-embedded": "^3.0.0", + "hast-util-is-element": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-minify-whitespace/node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/hast-util-minify-whitespace/node_modules/unist-util-is": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", + "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/hast-util-parse-selector": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz", @@ -11972,6 +12906,23 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/hast-util-phrasing": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/hast-util-phrasing/-/hast-util-phrasing-3.0.1.tgz", + "integrity": "sha512-6h60VfI3uBQUxHqTyMymMZnEbNl1XmEGtOxxKYL7stY2o601COo62AWAYBQR9lZbYXYSBoxag8UpPRXK+9fqSQ==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-embedded": "^3.0.0", + "hast-util-has-property": "^3.0.0", + "hast-util-is-body-ok-link": "^3.0.0", + "hast-util-is-element": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/hast-util-select": { "version": "6.0.4", "resolved": "https://registry.npmjs.org/hast-util-select/-/hast-util-select-6.0.4.tgz", @@ -12005,7 +12956,162 @@ "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", "license": "MIT" }, - "node_modules/hast-util-select/node_modules/unist-util-is": { + "node_modules/hast-util-select/node_modules/unist-util-is": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", + "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-select/node_modules/unist-util-visit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", + "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-select/node_modules/unist-util-visit-parents": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz", + "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-select/node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/hast-util-to-html": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz", + "integrity": "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-whitespace": "^3.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "stringify-entities": "^4.0.0", + "zwitch": "^2.0.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-html/node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/hast-util-to-html/node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/hast-util-to-html/node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/hast-util-to-mdast": { + "version": "10.1.2", + "resolved": "https://registry.npmjs.org/hast-util-to-mdast/-/hast-util-to-mdast-10.1.2.tgz", + "integrity": "sha512-FiCRI7NmOvM4y+f5w32jPRzcxDIz+PUqDwEqn1A+1q2cdp3B8Gx7aVrXORdOKjMNDQsD1ogOr896+0jJHW1EFQ==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "hast-util-phrasing": "^3.0.0", + "hast-util-to-html": "^9.0.0", + "hast-util-to-text": "^4.0.0", + "hast-util-whitespace": "^3.0.0", + "mdast-util-phrasing": "^4.0.0", + "mdast-util-to-hast": "^13.0.0", + "mdast-util-to-string": "^4.0.0", + "rehype-minify-whitespace": "^6.0.0", + "trim-trailing-lines": "^2.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-mdast/node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/hast-util-to-mdast/node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/hast-util-to-mdast/node_modules/mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-mdast/node_modules/unist-util-is": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==", @@ -12018,7 +13124,7 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/hast-util-select/node_modules/unist-util-visit": { + "node_modules/hast-util-to-mdast/node_modules/unist-util-visit": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", @@ -12033,7 +13139,7 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/hast-util-select/node_modules/unist-util-visit-parents": { + "node_modules/hast-util-to-mdast/node_modules/unist-util-visit-parents": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz", "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==", @@ -12047,65 +13153,6 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/hast-util-select/node_modules/zwitch": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", - "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/hast-util-to-html": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz", - "integrity": "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "@types/unist": "^3.0.0", - "ccount": "^2.0.0", - "comma-separated-tokens": "^2.0.0", - "hast-util-whitespace": "^3.0.0", - "html-void-elements": "^3.0.0", - "mdast-util-to-hast": "^13.0.0", - "property-information": "^7.0.0", - "space-separated-tokens": "^2.0.0", - "stringify-entities": "^4.0.0", - "zwitch": "^2.0.4" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-to-html/node_modules/@types/unist": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", - "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", - "license": "MIT" - }, - "node_modules/hast-util-to-html/node_modules/ccount": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", - "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/hast-util-to-html/node_modules/zwitch": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", - "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/hast-util-to-string": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/hast-util-to-string/-/hast-util-to-string-3.0.1.tgz", @@ -13254,10 +14301,9 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -13775,6 +14821,12 @@ "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", "license": "MIT" }, + "node_modules/lodash.iteratee": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash.iteratee/-/lodash.iteratee-4.7.0.tgz", + "integrity": "sha512-yv3cSQZmfpbIKo4Yo45B1taEvxjNvcpF1CEOc0Y6dEyvhPIfEJE3twDwPgWTPQubcSgXyBwBKG6wpQvWMDOf6Q==", + "license": "MIT" + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -14000,7 +15052,6 @@ "integrity": "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==", "dev": true, "license": "MIT", - "peer": true, "bin": { "marked": "bin/marked.js" }, @@ -14107,6 +15158,206 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/mdast-util-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-footnote/node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/mdast-util-gfm-footnote/node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/mdast-util-gfm-footnote/node_modules/longest-streak": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", + "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/mdast-util-gfm-footnote/node_modules/mdast-util-from-markdown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz", + "integrity": "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark": "^4.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-footnote/node_modules/mdast-util-to-markdown": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", + "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "longest-streak": "^3.0.0", + "mdast-util-phrasing": "^4.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-footnote/node_modules/mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-footnote/node_modules/micromark": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", + "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-gfm-footnote/node_modules/unist-util-is": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", + "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-footnote/node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-footnote/node_modules/unist-util-visit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", + "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-footnote/node_modules/unist-util-visit-parents": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz", + "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-footnote/node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/mdast-util-gfm-strikethrough": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-0.2.3.tgz", @@ -14136,14 +15387,56 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/mdast-util-gfm-task-list-item": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-0.1.6.tgz", - "integrity": "sha512-/d51FFIfPsSmCIRNp7E6pozM9z1GYPIkSy1urQ8s/o4TC22BZ7DqfHFWiqBD23bc7J3vV1Fc9O4QIHBlfuit8A==", - "dev": true, + "node_modules/mdast-util-gfm-task-list-item": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-0.1.6.tgz", + "integrity": "sha512-/d51FFIfPsSmCIRNp7E6pozM9z1GYPIkSy1urQ8s/o4TC22BZ7DqfHFWiqBD23bc7J3vV1Fc9O4QIHBlfuit8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "mdast-util-to-markdown": "~0.6.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-phrasing": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", + "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-phrasing/node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/mdast-util-phrasing/node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/mdast-util-phrasing/node_modules/unist-util-is": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", + "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==", "license": "MIT", "dependencies": { - "mdast-util-to-markdown": "~0.6.0" + "@types/unist": "^3.0.0" }, "funding": { "type": "opencollective", @@ -14359,100 +15652,414 @@ ], "license": "MIT", "dependencies": { - "debug": "^4.0.0", - "parse-entities": "^2.0.0" + "debug": "^4.0.0", + "parse-entities": "^2.0.0" + } + }, + "node_modules/micromark-core-commonmark": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", + "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-gfm": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-0.3.3.tgz", + "integrity": "sha512-oVN4zv5/tAIA+l3GbMi7lWeYpJ14oQyJ3uEim20ktYFAcfX1x3LNlFGGlmrZHt7u9YlKExmyJdDGaTt6cMSR/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "micromark": "~2.11.0", + "micromark-extension-gfm-autolink-literal": "~0.5.0", + "micromark-extension-gfm-strikethrough": "~0.6.5", + "micromark-extension-gfm-table": "~0.4.0", + "micromark-extension-gfm-tagfilter": "~0.3.0", + "micromark-extension-gfm-task-list-item": "~0.3.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-autolink-literal": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-0.5.7.tgz", + "integrity": "sha512-ePiDGH0/lhcngCe8FtH4ARFoxKTUelMp4L7Gg2pujYD5CSMb9PbblnyL+AAMud/SNMyusbS2XDSiPIRcQoNFAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "micromark": "~2.11.3" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-strikethrough": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-0.6.5.tgz", + "integrity": "sha512-PpOKlgokpQRwUesRwWEp+fHjGGkZEejj83k9gU5iXCbDG+XBA92BqnRKYJdfqfkrRcZRgGuPuXb7DaK/DmxOhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "micromark": "~2.11.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-table": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-0.4.3.tgz", + "integrity": "sha512-hVGvESPq0fk6ALWtomcwmgLvH8ZSVpcPjzi0AjPclB9FsVRgMtGZkUcpE0zgjOCFAznKepF4z3hX8z6e3HODdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "micromark": "~2.11.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-tagfilter": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-0.3.0.tgz", + "integrity": "sha512-9GU0xBatryXifL//FJH+tAZ6i240xQuFrSL7mYi8f4oZSbc+NvXjkrHemeYP0+L4ZUT+Ptz3b95zhUZnMtoi/Q==", + "dev": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-task-list-item": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-0.3.3.tgz", + "integrity": "sha512-0zvM5iSLKrc/NQl84pZSjGo66aTGd57C1idmlWmE87lkMcXrTxg1uXa/nXomxJytoje9trP0NDLvw4bZ/Z/XCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "micromark": "~2.11.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-factory-destination": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-label": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-title": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-chunked": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" } }, - "node_modules/micromark-extension-gfm": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-0.3.3.tgz", - "integrity": "sha512-oVN4zv5/tAIA+l3GbMi7lWeYpJ14oQyJ3uEim20ktYFAcfX1x3LNlFGGlmrZHt7u9YlKExmyJdDGaTt6cMSR/A==", - "dev": true, + "node_modules/micromark-util-classify-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], "license": "MIT", "dependencies": { - "micromark": "~2.11.0", - "micromark-extension-gfm-autolink-literal": "~0.5.0", - "micromark-extension-gfm-strikethrough": "~0.6.5", - "micromark-extension-gfm-table": "~0.4.0", - "micromark-extension-gfm-tagfilter": "~0.3.0", - "micromark-extension-gfm-task-list-item": "~0.3.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" } }, - "node_modules/micromark-extension-gfm-autolink-literal": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-0.5.7.tgz", - "integrity": "sha512-ePiDGH0/lhcngCe8FtH4ARFoxKTUelMp4L7Gg2pujYD5CSMb9PbblnyL+AAMud/SNMyusbS2XDSiPIRcQoNFAw==", - "dev": true, + "node_modules/micromark-util-combine-extensions": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], "license": "MIT", "dependencies": { - "micromark": "~2.11.3" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" } }, - "node_modules/micromark-extension-gfm-strikethrough": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-0.6.5.tgz", - "integrity": "sha512-PpOKlgokpQRwUesRwWEp+fHjGGkZEejj83k9gU5iXCbDG+XBA92BqnRKYJdfqfkrRcZRgGuPuXb7DaK/DmxOhw==", - "dev": true, + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], "license": "MIT", "dependencies": { - "micromark": "~2.11.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "micromark-util-symbol": "^2.0.0" } }, - "node_modules/micromark-extension-gfm-table": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-0.4.3.tgz", - "integrity": "sha512-hVGvESPq0fk6ALWtomcwmgLvH8ZSVpcPjzi0AjPclB9FsVRgMtGZkUcpE0zgjOCFAznKepF4z3hX8z6e3HODdA==", - "dev": true, + "node_modules/micromark-util-decode-string": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", + "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], "license": "MIT", "dependencies": { - "micromark": "~2.11.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-symbol": "^2.0.0" } }, - "node_modules/micromark-extension-gfm-tagfilter": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-0.3.0.tgz", - "integrity": "sha512-9GU0xBatryXifL//FJH+tAZ6i240xQuFrSL7mYi8f4oZSbc+NvXjkrHemeYP0+L4ZUT+Ptz3b95zhUZnMtoi/Q==", - "dev": true, - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } + "node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" }, - "node_modules/micromark-extension-gfm-task-list-item": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-0.3.3.tgz", - "integrity": "sha512-0zvM5iSLKrc/NQl84pZSjGo66aTGd57C1idmlWmE87lkMcXrTxg1uXa/nXomxJytoje9trP0NDLvw4bZ/Z/XCQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "micromark": "~2.11.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } + "node_modules/micromark-util-html-tag-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" }, - "node_modules/micromark-util-character": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "node_modules/micromark-util-normalize-identifier": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", "funding": [ { "type": "GitHub Sponsors", @@ -14465,14 +16072,13 @@ ], "license": "MIT", "dependencies": { - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" + "micromark-util-symbol": "^2.0.0" } }, - "node_modules/micromark-util-encode": { + "node_modules/micromark-util-resolve-all": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", - "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", "funding": [ { "type": "GitHub Sponsors", @@ -14483,7 +16089,10 @@ "url": "https://opencollective.com/unified" } ], - "license": "MIT" + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + } }, "node_modules/micromark-util-sanitize-uri": { "version": "2.0.1", @@ -14506,6 +16115,28 @@ "micromark-util-symbol": "^2.0.0" } }, + "node_modules/micromark-util-subtokenize": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", + "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, "node_modules/micromark-util-symbol": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", @@ -17229,7 +18860,6 @@ "dev": true, "inBundle": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -18445,6 +20075,20 @@ "node": ">=14" } }, + "node_modules/rehype-minify-whitespace": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/rehype-minify-whitespace/-/rehype-minify-whitespace-6.0.2.tgz", + "integrity": "sha512-Zk0pyQ06A3Lyxhe9vGtOtzz3Z0+qZ5+7icZ/PL/2x1SHPbKao5oB/g/rlc6BCTajqBb33JcOe71Ye1oFsuYbnw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-minify-whitespace": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/rehype-parse": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/rehype-parse/-/rehype-parse-9.0.1.tgz", @@ -18844,7 +20488,6 @@ "integrity": "sha512-6qGjWccl5yoyugHt3jTgztJ9Y0JVzyH8/Voc/D8PlLat9pwxQYXz7W1Dpnq5h0/G5GCYGUaDSlYcyk3AMh5A6g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@semantic-release/commit-analyzer": "^13.0.1", "@semantic-release/error": "^4.0.0", @@ -20483,7 +22126,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -20586,6 +22228,16 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/trim-trailing-lines": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/trim-trailing-lines/-/trim-trailing-lines-2.1.0.tgz", + "integrity": "sha512-5UR5Biq4VlVOtzqkm2AZlgvSlDJtME46uV0br0gENbwN4l5+mMKT4b9gJKqWtuL2zAIqajGJGuvbCbcAJUZqBg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/trough": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/trough/-/trough-1.0.5.tgz", @@ -20892,6 +22544,21 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/unist-util-find": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unist-util-find/-/unist-util-find-3.0.0.tgz", + "integrity": "sha512-T7ZqS7immLjYyC4FCp2hDo3ksZ1v+qcbb+e5+iWxc2jONgHOLXPCpms1L8VV4hVxCXgWTxmBHDztuEZFVwC+Gg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "lodash.iteratee": "^4.0.0", + "unist-util-visit": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/unist-util-find-after": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/unist-util-find-after/-/unist-util-find-after-5.0.0.tgz", @@ -20925,6 +22592,54 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/unist-util-find/node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/unist-util-find/node_modules/unist-util-is": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", + "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-find/node_modules/unist-util-visit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", + "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-find/node_modules/unist-util-visit-parents": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz", + "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/unist-util-is": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-4.1.0.tgz", diff --git a/package.json b/package.json index ff849f7..fc0d6cd 100644 --- a/package.json +++ b/package.json @@ -54,6 +54,7 @@ "@adobe/helix-admin-support": "5.0.3", "@adobe/helix-config": "5.7.0", "@adobe/helix-google-support": "4.0.1", + "@adobe/helix-html2md": "1.1.0", "@adobe/helix-mediahandler": "2.9.5", "@adobe/helix-onedrive-support": "12.1.4", "@adobe/helix-shared-body-data": "2.2.2", diff --git a/src/contentproxy/index.js b/src/contentproxy/index.js index ce95ceb..2556b8b 100644 --- a/src/contentproxy/index.js +++ b/src/contentproxy/index.js @@ -17,6 +17,7 @@ import { error } from './errors.js'; import google from './google.js'; import markup from './markup.js'; import onedrive from './onedrive.js'; +import sourcebus from './sourcebus.js'; /** * @type {import('./contentproxy').ContentSourceHandler[]} @@ -25,6 +26,7 @@ export const HANDLERS = { // exported for testing only google, onedrive, markup, + sourcebus, }; /** @@ -34,7 +36,11 @@ export const HANDLERS = { // exported for testing only * @return {import('./contentproxy').ContentSourceHandler} handler */ export function getContentSourceHandler(source) { - return HANDLERS[source.type]; + let { type } = source; + if (type === source.url.startsWith('https://api.aem.live/')) { + type = 'sourcebus'; + } + return HANDLERS[type]; } /** diff --git a/src/contentproxy/sourcebus.js b/src/contentproxy/sourcebus.js new file mode 100644 index 0000000..27da617 --- /dev/null +++ b/src/contentproxy/sourcebus.js @@ -0,0 +1,94 @@ +/* + * Copyright 2025 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ +import { updateMarkupSourceInfo } from './utils.js'; +import { errorResponse } from '../support/utils.js'; +import { error } from './errors.js'; +import { getSource } from '../source/get.js'; + +/** + * Retrieves a file from source bus. + * + * @param {import('../support/AdminContext').AdminContext} ctx context + * @param {import('../support/RequestInfo').RequestInfo} info request info + * @param {object} [opts] options + * @param {object} [opts.source] content source + * @param {string} [opts.lastModified] last modified + * @param {number} [opts.fetchTimeout] fetch timeout + * @returns {Promise} response + */ +async function handle(ctx, info, opts) { + const { config: { content }, log } = ctx; + + const source = opts?.source ?? content.source; + const sourceUrl = new URL(source.url); + // extract org and site from url.pathname, format: //sites//source + // e.g. /adobe/sites/foo/source + const pathMatch = sourceUrl.pathname.match(/^\/([^/]+)\/sites\/([^/]+)\/source/); + if (!pathMatch) { + return errorResponse(log, 400, error( + 'Invalid source URL: $1', + sourceUrl.href, + )); + } + const [, org, site] = pathMatch; // eslint-disable-line prefer-destructuring + + // for now, only allow source bus from the same org and site + if (org !== info.org || site !== info.site) { + return errorResponse(log, 400, error( + 'Source bus is not allowed for org: $1, site: $2', + org, + site, + )); + } + // the source is stored as .html files in the source bus + let sourcePath = info.resourcePath; + if (info.ext === '.md') { + sourcePath = `${sourcePath.substring(0, sourcePath.length - '.md'.length)}.html`; + } else if (!info.ext) { + sourcePath += '.html'; + } else { + return errorResponse(log, 400, error( + 'unexpected file extension: $1', + info.ext, + )); + } + + // load content from source bus + const sourceInfo = { + org, + site, + resourcePath: sourcePath, + }; + const response = await getSource(ctx, sourceInfo); + if (!response.ok) { + return response; + } + + // convert to md + // TODO: implement this + // const mdResponse = await convertToMd(ctx, info, response); + + updateMarkupSourceInfo(info.sourceInfo, response); + + return response; +} + +/** + * @type {import('./contentproxy.js').ContentSourceHandler} + */ +export default { + name: 'sourcebus', + handle, + handleJSON: () => { throw new Error('not implemented'); }, + handleFile: () => { throw new Error('not implemented'); }, + list: () => { throw new Error('not implemented'); }, +}; From f9f1b784dc4629f2df978c326ea280dc1a71d650 Mon Sep 17 00:00:00 2001 From: Tobias Bocanegra Date: Wed, 10 Dec 2025 16:21:18 +0100 Subject: [PATCH 02/11] chore: make tests happy --- src/contentproxy/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/contentproxy/index.js b/src/contentproxy/index.js index 2556b8b..eafae1a 100644 --- a/src/contentproxy/index.js +++ b/src/contentproxy/index.js @@ -37,7 +37,7 @@ export const HANDLERS = { // exported for testing only */ export function getContentSourceHandler(source) { let { type } = source; - if (type === source.url.startsWith('https://api.aem.live/')) { + if (type === source.url?.startsWith('https://api.aem.live/')) { type = 'sourcebus'; } return HANDLERS[type]; From d2cc7961de660dcb9eba988c0bc7028e2ed1ad94 Mon Sep 17 00:00:00 2001 From: Tobias Bocanegra Date: Thu, 11 Dec 2025 16:38:09 +0100 Subject: [PATCH 03/11] chore: update --- src/contentproxy/index.js | 2 +- src/contentproxy/sourcebus.js | 47 ++++---- .../fixtures/sourcebus/index.html | 11 ++ test/contentproxy/sourcebus.test.js | 100 ++++++++++++++++++ test/utils.d.ts | 1 + test/utils.js | 2 +- 6 files changed, 142 insertions(+), 21 deletions(-) create mode 100644 test/contentproxy/fixtures/sourcebus/index.html create mode 100644 test/contentproxy/sourcebus.test.js diff --git a/src/contentproxy/index.js b/src/contentproxy/index.js index eafae1a..a9464b9 100644 --- a/src/contentproxy/index.js +++ b/src/contentproxy/index.js @@ -37,7 +37,7 @@ export const HANDLERS = { // exported for testing only */ export function getContentSourceHandler(source) { let { type } = source; - if (type === source.url?.startsWith('https://api.aem.live/')) { + if (source.url?.startsWith('https://api.aem.live/')) { type = 'sourcebus'; } return HANDLERS[type]; diff --git a/src/contentproxy/sourcebus.js b/src/contentproxy/sourcebus.js index 27da617..8a65ed5 100644 --- a/src/contentproxy/sourcebus.js +++ b/src/contentproxy/sourcebus.js @@ -9,10 +9,11 @@ * OF ANY KIND, either express or implied. See the License for the specific language * governing permissions and limitations under the License. */ -import { updateMarkupSourceInfo } from './utils.js'; +import { HelixStorage } from '@adobe/helix-shared-storage'; +import { html2md } from '@adobe/helix-html2md'; +import { Response } from '@adobe/fetch'; import { errorResponse } from '../support/utils.js'; import { error } from './errors.js'; -import { getSource } from '../source/get.js'; /** * Retrieves a file from source bus. @@ -26,16 +27,16 @@ import { getSource } from '../source/get.js'; * @returns {Promise} response */ async function handle(ctx, info, opts) { - const { config: { content }, log } = ctx; + const { config: { content, limits }, log } = ctx; const source = opts?.source ?? content.source; const sourceUrl = new URL(source.url); - // extract org and site from url.pathname, format: //sites//source + // extract org and site from url.pathname, format: https://api.aem.live//sites//source // e.g. /adobe/sites/foo/source - const pathMatch = sourceUrl.pathname.match(/^\/([^/]+)\/sites\/([^/]+)\/source/); + const pathMatch = sourceUrl.pathname.match(/^\/([^/]+)\/sites\/([^/]+)\/source$/); if (!pathMatch) { return errorResponse(log, 400, error( - 'Invalid source URL: $1', + 'Source url must be in the format: https://api.aem.live//sites//source. Got: $1', sourceUrl.href, )); } @@ -63,23 +64,31 @@ async function handle(ctx, info, opts) { } // load content from source bus - const sourceInfo = { - org, - site, - resourcePath: sourcePath, - }; - const response = await getSource(ctx, sourceInfo); - if (!response.ok) { - return response; + const sourceBus = HelixStorage.fromContext(ctx).sourceBus(); + const meta = {}; + const body = await sourceBus.get(`${org}/${site}${sourcePath}`, meta); + if (!body) { + return new Response('', { status: 404 }); } // convert to md - // TODO: implement this - // const mdResponse = await convertToMd(ctx, info, response); - - updateMarkupSourceInfo(info.sourceInfo, response); + const md = await html2md(body, { + mediaHandler: null, + log, + // url: sourceUrl, + org, + site, + unspreadLists: true, + maxImages: limits?.html2md?.maxImages, + }); - return response; + return new Response(md, { + status: 200, + headers: { + 'content-type': 'text/markdown', + 'last-modified': meta.LastModified?.toUTCString(), + }, + }); } /** diff --git a/test/contentproxy/fixtures/sourcebus/index.html b/test/contentproxy/fixtures/sourcebus/index.html new file mode 100644 index 0000000..36e19bb --- /dev/null +++ b/test/contentproxy/fixtures/sourcebus/index.html @@ -0,0 +1,11 @@ + + + +
+
+

Hello, world.

+

Testing, source bus.

+
+
+ + \ No newline at end of file diff --git a/test/contentproxy/sourcebus.test.js b/test/contentproxy/sourcebus.test.js new file mode 100644 index 0000000..440b5d3 --- /dev/null +++ b/test/contentproxy/sourcebus.test.js @@ -0,0 +1,100 @@ +/* + * Copyright 2025 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +/* eslint-env mocha */ +import { resolve } from 'path'; +import assert from 'assert'; +import { gunzip } from 'zlib'; +import { promisify } from 'util'; +import { Request } from '@adobe/fetch'; +import { AuthInfo } from '../../src/auth/auth-info.js'; +import { main } from '../../src/index.js'; +import { Nock, SITE_CONFIG } from '../utils.js'; + +const gunzipAsync = promisify(gunzip); + +const SITE_MUP_CONFIG = (url = 'https://api.aem.live/org/sites/site/source') => ({ + ...SITE_CONFIG, + content: { + ...SITE_CONFIG.content, + source: { + type: 'markup', + url, + }, + }, +}); + +describe('Source Bus Content Proxy Tests', () => { + /** @type {import('../utils.js').NockEnv} */ + let nock; + + beforeEach(() => { + nock = new Nock().env(); + }); + + afterEach(() => { + nock.done(); + }); + + function setupTest(path = '/', { + config = SITE_MUP_CONFIG(), data, + authInfo = AuthInfo.Default().withAuthenticated(true), + } = {}) { + nock.siteConfig(config); + + const suffix = `/org/sites/site/contentproxy${path}`; + const query = new URLSearchParams(data); + + const request = new Request(`https://api.aem.live${suffix}?${query}`, { + headers: { + 'x-request-id': 'rid', + }, + }); + const context = { + pathInfo: { suffix }, + attributes: { + authInfo, + maxAttempts: 1, + }, + runtime: { region: 'us-east-1' }, + env: { + HLX_CONFIG_SERVICE_TOKEN: 'token', + }, + }; + return { request, context }; + } + + it('Retrieves Document from source bus', async () => { + nock.source() + .getObject('/index.html') + .replyWithFile(200, resolve(__testdir, 'contentproxy/fixtures/sourcebus/index.html'), { + 'last-modified': 'Thu, 11 Dec 2025 12:00:00 GMT', + 'content-type': 'text/html', + }); + + const { request, context } = setupTest('/', { + config: { + ...SITE_MUP_CONFIG(), + }, + }); + const response = await main(request, context); + + assert.strictEqual(response.status, 200); + assert.strictEqual(await response.text(), '# Hello, world.\n\nTesting, source bus.\n'); + assert.deepStrictEqual(response.headers.plain(), { + 'content-type': 'text/markdown', + 'cache-control': 'no-store, private, must-revalidate', + vary: 'Accept-Encoding', + 'last-modified': 'Thu, 11 Dec 2025 12:00:00 GMT', + }); + }); +}); diff --git a/test/utils.d.ts b/test/utils.d.ts index 5b86b1b..a519e38 100644 --- a/test/utils.d.ts +++ b/test/utils.d.ts @@ -52,6 +52,7 @@ declare interface Nock { code(ref?: string): S3Nock; content(contentBusId?: string): S3Nock; media(contentBusId?: string): S3Nock; + source(org?: string, site?: string): S3Nock; sqs(queueName: string, entries?: any[]): nock.Scope; } diff --git a/test/utils.js b/test/utils.js index d9412ba..0ac5336 100644 --- a/test/utils.js +++ b/test/utils.js @@ -150,7 +150,7 @@ export function Nock() { nocker.media = (contentBusId) => nocker.s3('helix-media-bus', contentBusId ?? SITE_CONFIG.content.contentBusId); - nocker.source = () => nocker.s3('helix-source-bus', ''); + nocker.source = (org = 'org', site = 'site') => nocker.s3('helix-source-bus', `${org}/${site}`); nocker.siteConfig = (config, { org = 'org', site = 'site' } = {}) => { const scope = nocker('https://config.aem.page').get(`/main--${site}--${org}/config.json?scope=admin`); From 1093b7954595a38d5b7642c5f771bf604d0df57f Mon Sep 17 00:00:00 2001 From: Tobias Bocanegra Date: Fri, 12 Dec 2025 16:32:42 +0100 Subject: [PATCH 04/11] feat: add media handler --- src/contentproxy/errors.js | 4 + src/contentproxy/sourcebus.js | 89 ++++++-- test/contentproxy/fixtures/sourcebus/300.png | Bin 0 -> 838 bytes .../fixtures/sourcebus/gallery.html | 13 ++ .../fixtures/sourcebus/welcome.html | 11 + test/contentproxy/sourcebus.test.js | 197 +++++++++++++++++- 6 files changed, 290 insertions(+), 24 deletions(-) create mode 100644 test/contentproxy/fixtures/sourcebus/300.png create mode 100644 test/contentproxy/fixtures/sourcebus/gallery.html create mode 100644 test/contentproxy/fixtures/sourcebus/welcome.html diff --git a/src/contentproxy/errors.js b/src/contentproxy/errors.js index c7f300d..177562d 100644 --- a/src/contentproxy/errors.js +++ b/src/contentproxy/errors.js @@ -58,6 +58,10 @@ const errors = [ code: 'AEM_BACKEND_FILE_TOO_BIG', template: 'Unable to preview \'$1\': Documents larger than 100mb not supported: $2', }, + { + code: 'AEM_BACKEND_TOO_MANY_IMAGES', + template: 'Unable to preview \'$1\': Documents has more than $2 images: $3', + }, { code: 'AEM_BACKEND_RESOURCE_TOO_BIG', template: 'Files larger than 500mb are not supported: $1', diff --git a/src/contentproxy/sourcebus.js b/src/contentproxy/sourcebus.js index 8a65ed5..39451c6 100644 --- a/src/contentproxy/sourcebus.js +++ b/src/contentproxy/sourcebus.js @@ -10,11 +10,16 @@ * governing permissions and limitations under the License. */ import { HelixStorage } from '@adobe/helix-shared-storage'; -import { html2md } from '@adobe/helix-html2md'; +import { MediaHandler, SizeTooLargeException } from '@adobe/helix-mediahandler'; +import { ConstraintsError, html2md, TooManyImagesError } from '@adobe/helix-html2md'; import { Response } from '@adobe/fetch'; import { errorResponse } from '../support/utils.js'; import { error } from './errors.js'; +const DEFAULT_MAX_IMAGE_SIZE = 20 * 1024 * 1024; // 20mb + +const DEFAULT_MAX_IMAGES = 200; + /** * Retrieves a file from source bus. * @@ -54,9 +59,9 @@ async function handle(ctx, info, opts) { let sourcePath = info.resourcePath; if (info.ext === '.md') { sourcePath = `${sourcePath.substring(0, sourcePath.length - '.md'.length)}.html`; - } else if (!info.ext) { - sourcePath += '.html'; + /* c8 ignore next 7 */ } else { + // this should never happen, since all resourcePaths are properly mapped before return errorResponse(log, 400, error( 'unexpected file extension: $1', info.ext, @@ -71,24 +76,72 @@ async function handle(ctx, info, opts) { return new Response('', { status: 404 }); } - // convert to md - const md = await html2md(body, { - mediaHandler: null, + const { + MEDIAHANDLER_NOCACHHE: noCache, + CLOUDFLARE_ACCOUNT_ID: r2AccountId, + CLOUDFLARE_R2_ACCESS_KEY_ID: r2AccessKeyId, + CLOUDFLARE_R2_SECRET_ACCESS_KEY: r2SecretAccessKey, + } = ctx.env; + + const mediaHandler = new MediaHandler({ + r2AccountId, + r2AccessKeyId, + r2SecretAccessKey, + bucketId: ctx.attributes.bucketMap.media, + owner: org, + repo: site, + ref: 'main', + contentBusId: content.contentBusId, log, - // url: sourceUrl, - org, - site, - unspreadLists: true, - maxImages: limits?.html2md?.maxImages, + noCache, + fetchTimeout: 5000, // limit image fetches to 5s + forceHttp1: true, + maxSize: limits?.html2md?.maxImageSize ?? DEFAULT_MAX_IMAGE_SIZE, }); - return new Response(md, { - status: 200, - headers: { - 'content-type': 'text/markdown', - 'last-modified': meta.LastModified?.toUTCString(), - }, - }); + const maxImages = limits?.html2md?.maxImages ?? DEFAULT_MAX_IMAGES; + try { + // convert to md + const md = await html2md(body, { + mediaHandler, + log, + url: sourceUrl.href + sourcePath, // only used for logging + org, + site, + unspreadLists: true, + maxImages, + }); + + return new Response(md, { + status: 200, + headers: { + 'content-type': 'text/markdown', + 'last-modified': meta.LastModified?.toUTCString(), + }, + }); + } catch (e) { + if (e instanceof TooManyImagesError) { + return errorResponse(log, 409, error( + 'Unable to preview \'$1\': Documents has more than $2 images: $3', + sourcePath, + maxImages, + e.message, // todo: include num images in error + )); + } + if (e instanceof SizeTooLargeException) { + return errorResponse(log, 409, error( + 'Unable to preview \'$1\': $2', + sourcePath, + e.message, + )); + } + /* c8 ignore next 6 */ + return errorResponse(log, 500, error( + 'Unable to preview \'$1\': $2', + sourcePath, + e.message, + )); + } } /** diff --git a/test/contentproxy/fixtures/sourcebus/300.png b/test/contentproxy/fixtures/sourcebus/300.png new file mode 100644 index 0000000000000000000000000000000000000000..4c1d59504b3132b6cb1e45c54ec4bd9072991c65 GIT binary patch literal 838 zcmeAS@N?(olHy`uVBq!ia0y~yVAKI&7G|JGckpC4ASE5(6XJU2%o!ka)22=J_4PAn z&h+u|k(887OG`60Hf|I7#R`<;EbxddW?X?_wfUqO7?>`4x;TbZ%z1m=-v4%h zNLyjDb<4)Qe-kb$PB60KohtFxq@+(fr1Z#&rP>}VihWd19e=m;I^Vu+vwf!focHhg z>$Yw3;hoP^3T1%i;{m_#{It<|{Jgs?YxWuYX(#9FN1dFsT7jeO`>uT^9BzrWTYnbm z{8;*U=91-Zo0b??@!UND6x!dqtV> zq)tAb_N(8$_Ns%~7CeInnsgS=S`W~X4Q9Q%T=8ct6j^_9lYf+tu#gOwCRbnOG>(x_TJc|P&((Z^xTxc zt5a@9Oj{elxz#50(($~ndE3m+l|P-ry!T2LP-=R$`u0o`EYe1tMeola-1=ru(VL$4t3$$c-x|%j zT6vOfqn61v|GUY`H+5dC{+4{N<-PTEM%eSGl@))#rJj5p%U3q-DZl#HM8lKe)f(RS zlD4kaQ9pU@>*u`(<4(Q$7N37Z<&*UGJu-ZUj#IP^=FrOUo&2Pah=b+m+RNB zvx-uUK6hpJ&dMcr>z^LIt7A4fFfQoLo%LDO>#QvQE;RcawJFtVm&ME_RVp=-I)0by zY&qB8_?Z|O_AR~elV*VO!Sn0<*EQ{!wr1y)`$5cqr@h-=mi4)4r^EI$mZxu>l&b&v v>-6W6n|`^cQzO*MCASOVNo5QS4YmIn=IEDoUlrC92PyP)^>bP0l+XkKIz)+p literal 0 HcmV?d00001 diff --git a/test/contentproxy/fixtures/sourcebus/gallery.html b/test/contentproxy/fixtures/sourcebus/gallery.html new file mode 100644 index 0000000..cf6b2af --- /dev/null +++ b/test/contentproxy/fixtures/sourcebus/gallery.html @@ -0,0 +1,13 @@ + + + +
+
+

Hello, world.

+

source bus images.

+ + +
+
+ + \ No newline at end of file diff --git a/test/contentproxy/fixtures/sourcebus/welcome.html b/test/contentproxy/fixtures/sourcebus/welcome.html new file mode 100644 index 0000000..36e19bb --- /dev/null +++ b/test/contentproxy/fixtures/sourcebus/welcome.html @@ -0,0 +1,11 @@ + + + +
+
+

Hello, world.

+

Testing, source bus.

+
+
+ + \ No newline at end of file diff --git a/test/contentproxy/sourcebus.test.js b/test/contentproxy/sourcebus.test.js index 440b5d3..fc6a4c3 100644 --- a/test/contentproxy/sourcebus.test.js +++ b/test/contentproxy/sourcebus.test.js @@ -13,15 +13,11 @@ /* eslint-env mocha */ import { resolve } from 'path'; import assert from 'assert'; -import { gunzip } from 'zlib'; -import { promisify } from 'util'; import { Request } from '@adobe/fetch'; import { AuthInfo } from '../../src/auth/auth-info.js'; import { main } from '../../src/index.js'; import { Nock, SITE_CONFIG } from '../utils.js'; -const gunzipAsync = promisify(gunzip); - const SITE_MUP_CONFIG = (url = 'https://api.aem.live/org/sites/site/source') => ({ ...SITE_CONFIG, content: { @@ -38,7 +34,9 @@ describe('Source Bus Content Proxy Tests', () => { let nock; beforeEach(() => { - nock = new Nock().env(); + nock = new Nock().env({ + HELIX_STORAGE_DISABLE_R2: 'true', + }); }); afterEach(() => { @@ -68,12 +66,13 @@ describe('Source Bus Content Proxy Tests', () => { runtime: { region: 'us-east-1' }, env: { HLX_CONFIG_SERVICE_TOKEN: 'token', + MEDIAHANDLER_NOCACHHE: 'true', }, }; return { request, context }; } - it('Retrieves Document from source bus', async () => { + it('Retrieves root document from source bus', async () => { nock.source() .getObject('/index.html') .replyWithFile(200, resolve(__testdir, 'contentproxy/fixtures/sourcebus/index.html'), { @@ -97,4 +96,190 @@ describe('Source Bus Content Proxy Tests', () => { 'last-modified': 'Thu, 11 Dec 2025 12:00:00 GMT', }); }); + + it('Retrieves document from source bus', async () => { + nock.source() + .getObject('/welcome.html') + .replyWithFile(200, resolve(__testdir, 'contentproxy/fixtures/sourcebus/welcome.html'), { + 'last-modified': 'Thu, 11 Dec 2025 12:00:00 GMT', + 'content-type': 'text/html', + }); + + const { request, context } = setupTest('/welcome', { + config: { + ...SITE_MUP_CONFIG(), + }, + }); + const response = await main(request, context); + + assert.strictEqual(response.status, 200); + assert.strictEqual(await response.text(), '# Hello, world.\n\nTesting, source bus.\n'); + assert.deepStrictEqual(response.headers.plain(), { + 'content-type': 'text/markdown', + 'cache-control': 'no-store, private, must-revalidate', + vary: 'Accept-Encoding', + 'last-modified': 'Thu, 11 Dec 2025 12:00:00 GMT', + }); + }); + + it('Returns 404 if resource is not found in source bus', async () => { + nock.source() + .getObject('/missing.html') + .reply(404); + + const { request, context } = setupTest('/missing', { + config: { + ...SITE_MUP_CONFIG(), + }, + }); + const response = await main(request, context); + + assert.strictEqual(response.status, 404); + assert.deepStrictEqual(response.headers.plain(), { + 'content-type': 'text/plain; charset=utf-8', + 'cache-control': 'no-store, private, must-revalidate', + vary: 'Accept-Encoding', + }); + }); + + it('Rejects preview if source.url has the correct format', async () => { + const { request, context } = setupTest('/', { + config: { + ...SITE_MUP_CONFIG('https://api.aem.live/org/sites/status'), + }, + }); + const response = await main(request, context); + + assert.strictEqual(response.status, 400); + assert.deepStrictEqual(response.headers.plain(), { + 'content-type': 'text/plain; charset=utf-8', + 'cache-control': 'no-store, private, must-revalidate', + 'x-error': 'Source url must be in the format: https://api.aem.live//sites//source. Got: https://api.aem.live/org/sites/status', + }); + }); + + it('Rejects preview if not the same org/site', async () => { + const { request, context } = setupTest('/', { + config: { + ...SITE_MUP_CONFIG('https://api.aem.live/org/sites/another/source'), + }, + }); + const response = await main(request, context); + + assert.strictEqual(response.status, 400); + assert.deepStrictEqual(response.headers.plain(), { + 'content-type': 'text/plain; charset=utf-8', + 'cache-control': 'no-store, private, must-revalidate', + 'x-error': 'Source bus is not allowed for org: org, site: another', + }); + }); + + it('Retrieves document from source bus with external images', async () => { + nock.source() + .getObject('/gallery.html') + .replyWithFile(200, resolve(__testdir, 'contentproxy/fixtures/sourcebus/gallery.html'), { + 'last-modified': 'Thu, 11 Dec 2025 12:00:00 GMT', + 'content-type': 'text/html', + }); + nock('https://www.example.com') + .get('/image1.jpg') + .replyWithFile(200, resolve(__testdir, 'contentproxy/fixtures/sourcebus/300.png')) + .get('/image2.jpg') + .replyWithFile(200, resolve(__testdir, 'contentproxy/fixtures/sourcebus/300.png')); + nock.media('853bced1f82a05e9d27a8f63ecac59e70d9c14680dc5e417429f65e988f') + .head('/1c2e2c6c049ccf4b583431e14919687f3a39cc227') + .times(2) + .reply(404) + .putObject('/1c2e2c6c049ccf4b583431e14919687f3a39cc227') + .times(2) + .reply(201); + + const { request, context } = setupTest('/gallery', { + config: { + ...SITE_MUP_CONFIG(), + }, + }); + const response = await main(request, context); + + assert.strictEqual(response.status, 200); + assert.strictEqual(await response.text(), `# Hello, world. + +source bus images. + +![][image0] ![][image0] + +[image0]: https://main--site--org.aem.page/media_1c2e2c6c049ccf4b583431e14919687f3a39cc227.png#width=300&height=300 +`); + assert.deepStrictEqual(response.headers.plain(), { + 'content-type': 'text/markdown', + 'cache-control': 'no-store, private, must-revalidate', + vary: 'Accept-Encoding', + 'last-modified': 'Thu, 11 Dec 2025 12:00:00 GMT', + }); + }); + + it('Rejects document from source bus too many images', async () => { + nock.source() + .getObject('/gallery.html') + .replyWithFile(200, resolve(__testdir, 'contentproxy/fixtures/sourcebus/gallery.html'), { + 'last-modified': 'Thu, 11 Dec 2025 12:00:00 GMT', + 'content-type': 'text/html', + }); + + const { request, context } = setupTest('/gallery', { + config: { + ...SITE_MUP_CONFIG(), + limits: { + html2md: { + maxImages: 1, + }, + }, + }, + }); + const response = await main(request, context); + + assert.strictEqual(response.status, 409); + assert.strictEqual(await response.text(), ''); + assert.deepStrictEqual(response.headers.plain(), { + 'cache-control': 'no-store, private, must-revalidate', + 'content-type': 'text/plain; charset=utf-8', + 'x-error': "Unable to preview '/gallery.html': Documents has more than 1 images: maximum number of images reached: 2 of 1 max.", + 'x-error-code': 'AEM_BACKEND_TOO_MANY_IMAGES', + }); + }); + + it('Rejects document from source if image is too big', async () => { + nock.source() + .getObject('/gallery.html') + .replyWithFile(200, resolve(__testdir, 'contentproxy/fixtures/sourcebus/gallery.html'), { + 'last-modified': 'Thu, 11 Dec 2025 12:00:00 GMT', + 'content-type': 'text/html', + }); + + nock('https://www.example.com') + .get('/image1.jpg') + .replyWithFile(200, resolve(__testdir, 'contentproxy/fixtures/sourcebus/300.png')) + .get('/image2.jpg') + .replyWithFile(200, resolve(__testdir, 'contentproxy/fixtures/sourcebus/300.png')); + + const { request, context } = setupTest('/gallery', { + config: { + ...SITE_MUP_CONFIG(), + limits: { + html2md: { + maxImageSize: 100, + }, + }, + }, + }); + const response = await main(request, context); + + assert.strictEqual(response.status, 409); + assert.strictEqual(await response.text(), ''); + assert.deepStrictEqual(response.headers.plain(), { + 'cache-control': 'no-store, private, must-revalidate', + 'content-type': 'text/plain; charset=utf-8', + 'x-error': "Unable to preview '/gallery.html': Images 1 and 2 exceed allowed limit of 100B", + }); + }); }); From 08c7670c9198a0a7a7d3658db2e360cb965fbf3d Mon Sep 17 00:00:00 2001 From: Tobias Bocanegra Date: Wed, 17 Dec 2025 13:53:05 +0100 Subject: [PATCH 05/11] chore: more tests --- src/contentproxy/sourcebus.js | 1 + .../contentproxy/fixtures/sourcebus/gallery.html | 1 + test/contentproxy/sourcebus.test.js | 16 +++++++++------- test/utils.d.ts | 2 +- test/utils.js | 2 +- 5 files changed, 13 insertions(+), 9 deletions(-) diff --git a/src/contentproxy/sourcebus.js b/src/contentproxy/sourcebus.js index 39451c6..0261308 100644 --- a/src/contentproxy/sourcebus.js +++ b/src/contentproxy/sourcebus.js @@ -110,6 +110,7 @@ async function handle(ctx, info, opts) { site, unspreadLists: true, maxImages, + externalImageUrlPrefixes: [`https://main--${site}--${org}.aem.page/`], }); return new Response(md, { diff --git a/test/contentproxy/fixtures/sourcebus/gallery.html b/test/contentproxy/fixtures/sourcebus/gallery.html index cf6b2af..9fdb0e5 100644 --- a/test/contentproxy/fixtures/sourcebus/gallery.html +++ b/test/contentproxy/fixtures/sourcebus/gallery.html @@ -7,6 +7,7 @@

Hello, world.

source bus images.

+ diff --git a/test/contentproxy/sourcebus.test.js b/test/contentproxy/sourcebus.test.js index fc6a4c3..509a4fd 100644 --- a/test/contentproxy/sourcebus.test.js +++ b/test/contentproxy/sourcebus.test.js @@ -74,7 +74,7 @@ describe('Source Bus Content Proxy Tests', () => { it('Retrieves root document from source bus', async () => { nock.source() - .getObject('/index.html') + .getObject('/org/site/index.html') .replyWithFile(200, resolve(__testdir, 'contentproxy/fixtures/sourcebus/index.html'), { 'last-modified': 'Thu, 11 Dec 2025 12:00:00 GMT', 'content-type': 'text/html', @@ -99,7 +99,7 @@ describe('Source Bus Content Proxy Tests', () => { it('Retrieves document from source bus', async () => { nock.source() - .getObject('/welcome.html') + .getObject('/org/site/welcome.html') .replyWithFile(200, resolve(__testdir, 'contentproxy/fixtures/sourcebus/welcome.html'), { 'last-modified': 'Thu, 11 Dec 2025 12:00:00 GMT', 'content-type': 'text/html', @@ -124,7 +124,7 @@ describe('Source Bus Content Proxy Tests', () => { it('Returns 404 if resource is not found in source bus', async () => { nock.source() - .getObject('/missing.html') + .getObject('/org/site/missing.html') .reply(404); const { request, context } = setupTest('/missing', { @@ -176,7 +176,7 @@ describe('Source Bus Content Proxy Tests', () => { it('Retrieves document from source bus with external images', async () => { nock.source() - .getObject('/gallery.html') + .getObject('/org/site/gallery.html') .replyWithFile(200, resolve(__testdir, 'contentproxy/fixtures/sourcebus/gallery.html'), { 'last-modified': 'Thu, 11 Dec 2025 12:00:00 GMT', 'content-type': 'text/html', @@ -206,9 +206,11 @@ describe('Source Bus Content Proxy Tests', () => { source bus images. -![][image0] ![][image0] +![][image0] ![][image0] ![][image1] [image0]: https://main--site--org.aem.page/media_1c2e2c6c049ccf4b583431e14919687f3a39cc227.png#width=300&height=300 + +[image1]: https://main--site--org.aem.page/media_2c2e2c6c049ccf4b583431e14919687f3a39cc227.png#width=300&height=300 `); assert.deepStrictEqual(response.headers.plain(), { 'content-type': 'text/markdown', @@ -220,7 +222,7 @@ source bus images. it('Rejects document from source bus too many images', async () => { nock.source() - .getObject('/gallery.html') + .getObject('/org/site/gallery.html') .replyWithFile(200, resolve(__testdir, 'contentproxy/fixtures/sourcebus/gallery.html'), { 'last-modified': 'Thu, 11 Dec 2025 12:00:00 GMT', 'content-type': 'text/html', @@ -250,7 +252,7 @@ source bus images. it('Rejects document from source if image is too big', async () => { nock.source() - .getObject('/gallery.html') + .getObject('/org/site/gallery.html') .replyWithFile(200, resolve(__testdir, 'contentproxy/fixtures/sourcebus/gallery.html'), { 'last-modified': 'Thu, 11 Dec 2025 12:00:00 GMT', 'content-type': 'text/html', diff --git a/test/utils.d.ts b/test/utils.d.ts index a519e38..60ae38f 100644 --- a/test/utils.d.ts +++ b/test/utils.d.ts @@ -52,7 +52,7 @@ declare interface Nock { code(ref?: string): S3Nock; content(contentBusId?: string): S3Nock; media(contentBusId?: string): S3Nock; - source(org?: string, site?: string): S3Nock; + source(): S3Nock; sqs(queueName: string, entries?: any[]): nock.Scope; } diff --git a/test/utils.js b/test/utils.js index 0ac5336..d9412ba 100644 --- a/test/utils.js +++ b/test/utils.js @@ -150,7 +150,7 @@ export function Nock() { nocker.media = (contentBusId) => nocker.s3('helix-media-bus', contentBusId ?? SITE_CONFIG.content.contentBusId); - nocker.source = (org = 'org', site = 'site') => nocker.s3('helix-source-bus', `${org}/${site}`); + nocker.source = () => nocker.s3('helix-source-bus', ''); nocker.siteConfig = (config, { org = 'org', site = 'site' } = {}) => { const scope = nocker('https://config.aem.page').get(`/main--${site}--${org}/config.json?scope=admin`); From 2a4e2245747eecef28f70234aac8f1654eb7930f Mon Sep 17 00:00:00 2001 From: Tobias Bocanegra Date: Wed, 7 Jan 2026 12:27:32 +0100 Subject: [PATCH 06/11] feat: add json support --- src/contentproxy/sourcebus-json.js | 79 ++++++++++ src/contentproxy/sourcebus-utils.js | 68 +++++++++ src/contentproxy/sourcebus.js | 33 ++-- test/contentproxy/sourcebus-json.test.js | 182 +++++++++++++++++++++++ 4 files changed, 339 insertions(+), 23 deletions(-) create mode 100644 src/contentproxy/sourcebus-json.js create mode 100644 src/contentproxy/sourcebus-utils.js create mode 100644 test/contentproxy/sourcebus-json.test.js diff --git a/src/contentproxy/sourcebus-json.js b/src/contentproxy/sourcebus-json.js new file mode 100644 index 0000000..1b3ec75 --- /dev/null +++ b/src/contentproxy/sourcebus-json.js @@ -0,0 +1,79 @@ +/* + * Copyright 2026 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ +import { Response } from '@adobe/fetch'; +import { HelixStorage } from '@adobe/helix-shared-storage'; +import { errorResponse } from '../support/utils.js'; +import { assertValidSheetJSON } from './utils.js'; +import { validateSource } from './sourcebus-utils.js'; +import { error } from './errors.js'; + +function parseSheetJSON(data) { + let json; + try { + json = JSON.parse(data); + } catch { + throw Error('invalid sheet json; failed to parse'); + } + + assertValidSheetJSON(json); + return json; +} + +/** + * Fetches a JSON as sheet/multisheet from the external source. + * + * Falls back to code-bus if the content source does not have the resource. + * + * @param {import('../support/AdminContext').AdminContext} ctx context + * @param {import('../support/RequestInfo').RequestInfo} info request info + * @param {object} [opts] options + * @param {object} [opts.source] content source + * @param {string} [opts.lastModified] last modified + * @param {number} [opts.fetchTimeout] fetch timeout + * @returns {Promise} response + */ +export async function handleJSON(ctx, info, opts) { + const { log } = ctx; + const { + org, site, sourcePath, error: errorResp, + } = await validateSource(ctx, info, opts); + if (errorResp) { + return errorResp; + } + + // load content from source bus + const sourceBus = HelixStorage.fromContext(ctx).sourceBus(); + const meta = {}; + const body = await sourceBus.get(`${org}/${site}${sourcePath}`, meta); + if (!body) { + return new Response('', { status: 404 }); + } + + let json; + try { + json = parseSheetJSON(body); + } catch (e) { + return errorResponse(log, 400, error( + 'JSON fetched from markup \'$1\' is invalid: $2', + sourcePath, + e.message, + )); + } + + return new Response(JSON.stringify(json), { + status: 200, + headers: { + 'content-type': 'application/json', + 'last-modified': meta.LastModified?.toUTCString(), + }, + }); +} diff --git a/src/contentproxy/sourcebus-utils.js b/src/contentproxy/sourcebus-utils.js new file mode 100644 index 0000000..3dc1eef --- /dev/null +++ b/src/contentproxy/sourcebus-utils.js @@ -0,0 +1,68 @@ +/* + * Copyright 2026 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ +import { errorResponse } from '../support/utils.js'; +import { error } from './errors.js'; + +/** + * @typedef ValidationResult + * @property {string} org + * @property {string} site + * @property {URL} sourceUrl + * @property {string} sourcePath + * @property {Response} error + */ + +/** + * Validates if the content source is properly configured and if org/site match. + * + * @param {import('../support/AdminContext').AdminContext} ctx context + * @param {import('../support/RequestInfo').RequestInfo} info request info + * @param {object} [opts] options + * @param {object} [opts.source] content source + * @param {string} [opts.lastModified] last modified + * @param {number} [opts.fetchTimeout] fetch timeout + * @returns {Promise} the validation result + */ +export async function validateSource(ctx, info, opts) { + const { config: { content }, log } = ctx; + const source = opts?.source ?? content.source; + const sourceUrl = new URL(source.url); + const ret = { + sourceUrl, + org: info.org, + site: info.site, + sourcePath: info.resourcePath, + error: null, + }; + + // extract org and site from url.pathname, format: https://api.aem.live//sites//source + // e.g. /adobe/sites/foo/source + const pathMatch = sourceUrl.pathname.match(/^\/([^/]+)\/sites\/([^/]+)\/source$/); + if (!pathMatch) { + ret.error = errorResponse(log, 400, error( + 'Source url must be in the format: https://api.aem.live//sites//source. Got: $1', + sourceUrl.href, + )); + } else { + const [, org, site] = pathMatch; // eslint-disable-line prefer-destructuring + if (org !== info.org || site !== info.site) { + ret.error = errorResponse(log, 400, error( + 'Source bus is not allowed for org: $1, site: $2', + org, + site, + )); + } + } + + // for now, only allow source bus from the same org and site + return ret; +} diff --git a/src/contentproxy/sourcebus.js b/src/contentproxy/sourcebus.js index 0261308..6f4b43a 100644 --- a/src/contentproxy/sourcebus.js +++ b/src/contentproxy/sourcebus.js @@ -11,10 +11,12 @@ */ import { HelixStorage } from '@adobe/helix-shared-storage'; import { MediaHandler, SizeTooLargeException } from '@adobe/helix-mediahandler'; -import { ConstraintsError, html2md, TooManyImagesError } from '@adobe/helix-html2md'; +import { html2md, TooManyImagesError } from '@adobe/helix-html2md'; import { Response } from '@adobe/fetch'; +import { handleJSON } from './sourcebus-json.js'; +import { validateSource } from './sourcebus-utils.js'; import { errorResponse } from '../support/utils.js'; -import { error } from './errors.js'; +import {error} from "./errors.js"; const DEFAULT_MAX_IMAGE_SIZE = 20 * 1024 * 1024; // 20mb @@ -33,28 +35,13 @@ const DEFAULT_MAX_IMAGES = 200; */ async function handle(ctx, info, opts) { const { config: { content, limits }, log } = ctx; - - const source = opts?.source ?? content.source; - const sourceUrl = new URL(source.url); - // extract org and site from url.pathname, format: https://api.aem.live//sites//source - // e.g. /adobe/sites/foo/source - const pathMatch = sourceUrl.pathname.match(/^\/([^/]+)\/sites\/([^/]+)\/source$/); - if (!pathMatch) { - return errorResponse(log, 400, error( - 'Source url must be in the format: https://api.aem.live//sites//source. Got: $1', - sourceUrl.href, - )); + const { + org, site, sourceUrl, error: errorResp, + } = await validateSource(ctx, info, opts); + if (errorResp) { + return errorResp; } - const [, org, site] = pathMatch; // eslint-disable-line prefer-destructuring - // for now, only allow source bus from the same org and site - if (org !== info.org || site !== info.site) { - return errorResponse(log, 400, error( - 'Source bus is not allowed for org: $1, site: $2', - org, - site, - )); - } // the source is stored as .html files in the source bus let sourcePath = info.resourcePath; if (info.ext === '.md') { @@ -151,7 +138,7 @@ async function handle(ctx, info, opts) { export default { name: 'sourcebus', handle, - handleJSON: () => { throw new Error('not implemented'); }, + handleJSON, handleFile: () => { throw new Error('not implemented'); }, list: () => { throw new Error('not implemented'); }, }; diff --git a/test/contentproxy/sourcebus-json.test.js b/test/contentproxy/sourcebus-json.test.js new file mode 100644 index 0000000..646f513 --- /dev/null +++ b/test/contentproxy/sourcebus-json.test.js @@ -0,0 +1,182 @@ +/* + * Copyright 2025 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +/* eslint-env mocha */ +import assert from 'assert'; +import { Request } from '@adobe/fetch'; +import { AuthInfo } from '../../src/auth/auth-info.js'; +import { main } from '../../src/index.js'; +import { Nock, SITE_CONFIG } from '../utils.js'; +import { validSheet } from './utils.js'; + +const SITE_MUP_CONFIG = (url = 'https://api.aem.live/org/sites/site/source') => ({ + ...SITE_CONFIG, + content: { + ...SITE_CONFIG.content, + source: { + type: 'markup', + url, + }, + }, +}); + +describe('Source Bus Content Proxy Tests (JSON)', () => { + /** @type {import('../utils.js').NockEnv} */ + let nock; + + beforeEach(() => { + nock = new Nock().env({ + HELIX_STORAGE_DISABLE_R2: 'true', + }); + }); + + afterEach(() => { + nock.done(); + }); + + function setupTest(path = '/', { + config = SITE_MUP_CONFIG(), data, + authInfo = AuthInfo.Default().withAuthenticated(true), + } = {}) { + nock.siteConfig(config); + + const suffix = `/org/sites/site/contentproxy${path}`; + const query = new URLSearchParams(data); + + const request = new Request(`https://api.aem.live${suffix}?${query}`, { + headers: { + 'x-request-id': 'rid', + }, + }); + const context = { + pathInfo: { suffix }, + attributes: { + authInfo, + maxAttempts: 1, + }, + runtime: { region: 'us-east-1' }, + env: { + HLX_CONFIG_SERVICE_TOKEN: 'token', + MEDIAHANDLER_NOCACHHE: 'true', + }, + }; + return { request, context }; + } + + it('Retrieves json from source bus', async () => { + const sheet = validSheet(); + nock.source() + .getObject('/org/site/data.json') + .reply(200, sheet, { + 'last-modified': 'Thu, 11 Dec 2025 12:00:00 GMT', + 'content-type': 'text/html', + }); + + const { request, context } = setupTest('/data.json', { + config: { + ...SITE_MUP_CONFIG(), + }, + }); + const response = await main(request, context); + + assert.strictEqual(response.status, 200); + assert.deepStrictEqual(await response.json(), sheet); + assert.deepStrictEqual(response.headers.plain(), { + 'content-type': 'application/json', + 'cache-control': 'no-store, private, must-revalidate', + vary: 'Accept-Encoding', + 'last-modified': 'Thu, 11 Dec 2025 12:00:00 GMT', + }); + }); + + it('Rejects preview if source.url has the correct format', async () => { + const { request, context } = setupTest('/data.json', { + config: { + ...SITE_MUP_CONFIG('https://api.aem.live/org/sites/status'), + }, + }); + const response = await main(request, context); + + assert.strictEqual(response.status, 400); + assert.deepStrictEqual(response.headers.plain(), { + 'content-type': 'text/plain; charset=utf-8', + 'cache-control': 'no-store, private, must-revalidate', + 'x-error': 'Source url must be in the format: https://api.aem.live//sites//source. Got: https://api.aem.live/org/sites/status', + }); + }); + + it('Returns 404 if resource is not found in source bus', async () => { + nock.source() + .getObject('/org/site/missing.json') + .reply(404); + + const { request, context } = setupTest('/missing.json', { + config: { + ...SITE_MUP_CONFIG(), + }, + }); + const response = await main(request, context); + + assert.strictEqual(response.status, 404); + assert.deepStrictEqual(response.headers.plain(), { + 'content-type': 'text/plain; charset=utf-8', + 'cache-control': 'no-store, private, must-revalidate', + vary: 'Accept-Encoding', + }); + }); + + it('Rejects preview if json is invalid', async () => { + nock.source() + .getObject('/org/site/data.json') + .reply(200, 'not valid json', { + 'last-modified': 'Thu, 11 Dec 2025 12:00:00 GMT', + 'content-type': 'application/json', + }); + + const { request, context } = setupTest('/data.json', { + config: { + ...SITE_MUP_CONFIG(), + }, + }); + const response = await main(request, context); + + assert.strictEqual(response.status, 400); + assert.deepStrictEqual(response.headers.plain(), { + 'cache-control': 'no-store, private, must-revalidate', + 'content-type': 'text/plain; charset=utf-8', + 'x-error': "JSON fetched from markup '/data.json' is invalid: invalid sheet json; failed to parse", + }); + }); + + it('Rejects preview if json is not valid sheet', async () => { + nock.source() + .getObject('/org/site/data.json') + .reply(200, '[1,2,3]', { + 'last-modified': 'Thu, 11 Dec 2025 12:00:00 GMT', + 'content-type': 'application/json', + }); + + const { request, context } = setupTest('/data.json', { + config: { + ...SITE_MUP_CONFIG(), + }, + }); + const response = await main(request, context); + + assert.strictEqual(response.status, 400); + assert.deepStrictEqual(response.headers.plain(), { + 'cache-control': 'no-store, private, must-revalidate', + 'content-type': 'text/plain; charset=utf-8', + 'x-error': "JSON fetched from markup '/data.json' is invalid: invalid sheet; expecting object", + }); + }); +}); From 7f8534dcb12dddee876453682b8f6439c7709005 Mon Sep 17 00:00:00 2001 From: Tobias Bocanegra Date: Wed, 7 Jan 2026 16:23:34 +0100 Subject: [PATCH 07/11] feat: add sourcebus file support --- src/contentproxy/sourcebus-file.js | 50 +++++++++ src/contentproxy/sourcebus-json.js | 4 +- src/contentproxy/sourcebus.js | 5 +- test/contentproxy/sourcebus-file.test.js | 130 +++++++++++++++++++++++ 4 files changed, 184 insertions(+), 5 deletions(-) create mode 100644 src/contentproxy/sourcebus-file.js create mode 100644 test/contentproxy/sourcebus-file.test.js diff --git a/src/contentproxy/sourcebus-file.js b/src/contentproxy/sourcebus-file.js new file mode 100644 index 0000000..a1a6c16 --- /dev/null +++ b/src/contentproxy/sourcebus-file.js @@ -0,0 +1,50 @@ +/* + * Copyright 2026 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ +import { Response } from '@adobe/fetch'; +import { HelixStorage } from '@adobe/helix-shared-storage'; +import { validateSource } from './sourcebus-utils.js'; + +/** + * Fetches file data from the source bus + * + * @param {import('../support/AdminContext').AdminContext} ctx context + * @param {import('../support/RequestInfo').RequestInfo} info request info + * @param {object} [opts] options + * @param {object} [opts.source] content source + * @param {string} [opts.lastModified] last modified + * @param {number} [opts.fetchTimeout] fetch timeout + * @returns {Promise} response + */ +export async function handleFile(ctx, info, opts) { + const { + org, site, sourcePath, error: errorResp, + } = await validateSource(ctx, info, opts); + if (errorResp) { + return errorResp; + } + + // load content from source bus + const sourceBus = HelixStorage.fromContext(ctx).sourceBus(); + const meta = {}; + const body = await sourceBus.get(`${org}/${site}${sourcePath}`, meta); + if (!body) { + return new Response('', { status: 404 }); + } + + return new Response(body, { + status: 200, + headers: { + 'content-type': meta.ContentType, + 'last-modified': meta.LastModified?.toUTCString(), + }, + }); +} diff --git a/src/contentproxy/sourcebus-json.js b/src/contentproxy/sourcebus-json.js index 1b3ec75..79fa92e 100644 --- a/src/contentproxy/sourcebus-json.js +++ b/src/contentproxy/sourcebus-json.js @@ -29,9 +29,7 @@ function parseSheetJSON(data) { } /** - * Fetches a JSON as sheet/multisheet from the external source. - * - * Falls back to code-bus if the content source does not have the resource. + * Fetches a JSON as sheet/multisheet from the source bus * * @param {import('../support/AdminContext').AdminContext} ctx context * @param {import('../support/RequestInfo').RequestInfo} info request info diff --git a/src/contentproxy/sourcebus.js b/src/contentproxy/sourcebus.js index 6f4b43a..b2f3e74 100644 --- a/src/contentproxy/sourcebus.js +++ b/src/contentproxy/sourcebus.js @@ -14,9 +14,10 @@ import { MediaHandler, SizeTooLargeException } from '@adobe/helix-mediahandler'; import { html2md, TooManyImagesError } from '@adobe/helix-html2md'; import { Response } from '@adobe/fetch'; import { handleJSON } from './sourcebus-json.js'; +import { handleFile } from './sourcebus-file.js'; import { validateSource } from './sourcebus-utils.js'; import { errorResponse } from '../support/utils.js'; -import {error} from "./errors.js"; +import { error } from './errors.js'; const DEFAULT_MAX_IMAGE_SIZE = 20 * 1024 * 1024; // 20mb @@ -139,6 +140,6 @@ export default { name: 'sourcebus', handle, handleJSON, - handleFile: () => { throw new Error('not implemented'); }, + handleFile, list: () => { throw new Error('not implemented'); }, }; diff --git a/test/contentproxy/sourcebus-file.test.js b/test/contentproxy/sourcebus-file.test.js new file mode 100644 index 0000000..a27fa85 --- /dev/null +++ b/test/contentproxy/sourcebus-file.test.js @@ -0,0 +1,130 @@ +/* + * Copyright 2025 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +/* eslint-env mocha */ +import assert from 'assert'; +import { resolve } from 'path'; +import { Request } from '@adobe/fetch'; +import { AuthInfo } from '../../src/auth/auth-info.js'; +import { main } from '../../src/index.js'; +import { Nock, SITE_CONFIG } from '../utils.js'; + +const SITE_MUP_CONFIG = (url = 'https://api.aem.live/org/sites/site/source') => ({ + ...SITE_CONFIG, + content: { + ...SITE_CONFIG.content, + source: { + type: 'markup', + url, + }, + }, +}); + +describe('Source Bus Content Proxy Tests (JSON)', () => { + /** @type {import('../utils.js').NockEnv} */ + let nock; + + beforeEach(() => { + nock = new Nock().env({ + HELIX_STORAGE_DISABLE_R2: 'true', + }); + }); + + afterEach(() => { + nock.done(); + }); + + function setupTest(path = '/', { + config = SITE_MUP_CONFIG(), data, + authInfo = AuthInfo.Default().withAuthenticated(true), + } = {}) { + nock.siteConfig(config); + + const suffix = `/org/sites/site/contentproxy${path}`; + const query = new URLSearchParams(data); + + const request = new Request(`https://api.aem.live${suffix}?${query}`, { + headers: { + 'x-request-id': 'rid', + }, + }); + const context = { + pathInfo: { suffix }, + attributes: { + authInfo, + maxAttempts: 1, + }, + runtime: { region: 'us-east-1' }, + env: { + HLX_CONFIG_SERVICE_TOKEN: 'token', + MEDIAHANDLER_NOCACHHE: 'true', + }, + }; + return { request, context }; + } + + it('Retrieves pdf from source bus', async () => { + nock.source() + .getObject('/org/site/empty.pdf') + .replyWithFile(200, resolve(__testdir, 'contentproxy/fixtures/empty.pdf'), { + 'last-modified': 'Thu, 11 Dec 2025 12:00:00 GMT', + 'content-type': 'application/pdf', + }); + + const { request, context } = setupTest('/empty.pdf'); + const response = await main(request, context); + + assert.strictEqual(response.status, 200); + assert.deepStrictEqual(response.headers.plain(), { + 'content-type': 'application/pdf', + 'cache-control': 'no-store, private, must-revalidate', + vary: 'Accept-Encoding', + 'last-modified': 'Thu, 11 Dec 2025 12:00:00 GMT', + }); + }); + + it('Rejects preview if source.url has the correct format', async () => { + const { request, context } = setupTest('/data.pdf', { + config: { + ...SITE_MUP_CONFIG('https://api.aem.live/org/sites/status'), + }, + }); + const response = await main(request, context); + + assert.strictEqual(response.status, 400); + assert.deepStrictEqual(response.headers.plain(), { + 'content-type': 'text/plain; charset=utf-8', + 'cache-control': 'no-store, private, must-revalidate', + 'x-error': 'Source url must be in the format: https://api.aem.live//sites//source. Got: https://api.aem.live/org/sites/status', + }); + }); + + it('Returns 404 if resource is not found in source bus', async () => { + nock.source() + .getObject('/org/site/missing.pdf') + .reply(404); + + const { request, context } = setupTest('/missing.pdf', { + config: { + ...SITE_MUP_CONFIG(), + }, + }); + const response = await main(request, context); + + assert.strictEqual(response.status, 404); + assert.deepStrictEqual(response.headers.plain(), { + 'content-type': 'text/plain; charset=utf-8', + 'cache-control': 'no-store, private, must-revalidate', + vary: 'Accept-Encoding', + }); + }); +}); From ac53696ec81b71f8ac373581d22e5ae6a7ac7877 Mon Sep 17 00:00:00 2001 From: Tobias Bocanegra Date: Wed, 7 Jan 2026 16:52:24 +0100 Subject: [PATCH 08/11] wip --- src/contentproxy/sourcebus-list.js | 214 +++++++++++++++++++++++++++++ src/contentproxy/sourcebus.js | 3 +- 2 files changed, 216 insertions(+), 1 deletion(-) create mode 100644 src/contentproxy/sourcebus-list.js diff --git a/src/contentproxy/sourcebus-list.js b/src/contentproxy/sourcebus-list.js new file mode 100644 index 0000000..1fbc68a --- /dev/null +++ b/src/contentproxy/sourcebus-list.js @@ -0,0 +1,214 @@ +/* + * Copyright 2025 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ +import { sanitizeName, splitByExtension, editDistance } from '@adobe/helix-shared-string'; +import { GoogleClient } from '@adobe/helix-google-support'; +import { Forest } from './Forest.js'; +import {HelixStorage} from "@adobe/helix-shared-storage"; +import {getS3Key} from "../source/utils.js"; +import {Response} from "@adobe/fetch"; +import {createErrorResponse} from "../contentbus/utils.js"; +import {validateSource} from "./sourcebus-utils.js"; +import {errorResponse} from "../support/utils.js"; +import {error} from "./errors.js"; + +export class SourceForest extends Forest { + constructor(ctx) { + super(ctx.log); + this.ctx = ctx; + } + + /** + * List items below a root item. + * @returns {Promise} + */ + async listFolder(rootItem, rootPath, relPath) { + + + + + + + const bucket = HelixStorage.fromContext(context).sourceBus(); + const { org, site, rawPath: path } = info; + + try { + validateFolderPath(path); + const key = getS3Key(org, site, path); + const list = await bucket.list(key, { shallow: true, includePrefixes: true }); + + // Check the length of the raw filesList. This will include the + // directory marker files. So a directory with just a marker file + // is reported as empty, but without anything is reported as not found. + if (list.length === 0) { + return new Response('', { status: 404 }); + } + + if (headRequest) { + return new Response('', { status: 200 }); + } + + const output = transformList(list); + const headers = { + 'Content-Type': 'application/json', + }; + return new Response(JSON.stringify(output), { status: 200, headers }); + } catch (e) { + const opts = { e, log }; + opts.status = e.$metadata?.httpStatusCode; + return createErrorResponse(opts); + } + + + + + + const { log, client } = this; + const parentPath = `${rootPath}${relPath}`; + log.debug(`listing children for ${rootItem.id}:${parentPath}`); + let folderId = rootItem.id; + if (relPath) { + const hierarchy = await client.getItemsFromPath(rootItem.id, relPath, 'application/vnd.google-apps.folder'); + if (!hierarchy.length) { + return []; + } + folderId = hierarchy[0].id; + } + const opts = { + q: `'${folderId}' in parents and trashed=false`, + fields: 'nextPageToken, files(id, name, mimeType, modifiedTime, size)', + includeItemsFromAllDrives: true, + supportsAllDrives: true, + pageSize: 1000, + }; + + let itemList = []; + do { + // eslint-disable-next-line no-await-in-loop + const { data } = await client.drive.files.list(opts); + if (data.nextPageToken) { + opts.pageToken = data.nextPageToken; + } else { + opts.pageToken = null; + } + log.debug(`fetched ${data.files.length} items below ${folderId}. nextPageToken=${opts.pageToken ? '****' : 'null'}`); + itemList = itemList.concat(data.files); + } while (opts.pageToken); + + // sanitize names and filter out duplicates. + const map = new Map(); + for (const item of itemList) { + if (item.name === '.helix') { + // eslint-disable-next-line no-continue + continue; + } + const [itemName, ext] = splitByExtension(item.name); + const name = sanitizeName(itemName); + item.sanitizedName = ext ? `${name}.${ext}` : name; + item.file = true; + if (item.sanitizedName === 'index') { + item.path = `${parentPath}/`; + item.resourcePath = `${parentPath}/index.md`; + item.ext = '.md'; + } else if (item.mimeType === GoogleClient.TYPE_DOCUMENT) { + item.path = `${parentPath}/${name}`; + item.resourcePath = `${item.path}.md`; + item.ext = '.md'; + } else if (item.mimeType === GoogleClient.TYPE_SPREADSHEET) { + item.path = `${parentPath}/${name}.json`; + item.resourcePath = item.path; + item.ext = '.json'; + } else if (item.mimeType === 'application/vnd.google-apps.folder') { + delete item.file; + item.path = `${parentPath}/${name}`; + item.resourcePath = item.path; + } else if (ext === 'md') { + // ignore markdown files + // eslint-disable-next-line no-continue + continue; + } else { + item.path = `${parentPath}/${item.sanitizedName}`; + item.resourcePath = item.path; + item.ext = ext; + } + item.fuzzyDistance = editDistance(item.sanitizedName, itemName); + item.size = Number.parseInt(item.size, 10); + const existing = map.get(item.sanitizedName); + if (!existing || existing.fuzzyDistance > item.fuzzyDistance) { + map.set(item.sanitizedName, item); + } + } + log.info(`loaded ${map.size} children from ${rootItem.id}:${parentPath}`); + return Array.from(map.values()); + } +} +/** + * Fetches file data from the external source. + * the paths can specify the files that should be included in the list. if a path ends with `/*` + * its entire subtree is retrieved. + * + * @type {import('./contentproxy.js').FetchList} + * @param {import('../support/AdminContext').AdminContext} ctx context + * @param {import('../support/RequestInfo').RequestInfo} info request info + * @param {string[]} paths + * @param {ProgressCallback} progressCB + * @returns {Promise} the list of resources + */ +export async function list(ctx, paths, progressCB) { + const { config: { content: { source } }, log } = ctx; + const sourceUrl = new URL(source.url); + + // extract org and site from url.pathname, format: https://api.aem.live//sites//source + // e.g. /adobe/sites/foo/source + const pathMatch = sourceUrl.pathname.match(/^\/([^/]+)\/sites\/([^/]+)\/source$/); + if (!pathMatch) { + const { message } = error( + 'Source url must be in the format: https://api.aem.live//sites//source. Got: $1', + sourceUrl.href, + ); + throw new StatusCodeError(400, message); + } else { + const [, org, site] = pathMatch; // eslint-disable-line prefer-destructuring + if (org !== info.org || site !== info.site) { + ret.error = errorResponse(log, 400, error( + 'Source bus is not allowed for org: $1, site: $2', + org, + site, + )); + } + } + + // for now, only allow source bus from the same org and site + + + + + const forest = new SourceForest(ctx); + const itemList = await forest.generate(source, paths, progressCB); + + return itemList.map((item) => { + if (item.status) { + return item; + } + return { + path: item.path, + resourcePath: item.resourcePath, + source: { + name: item.name, + id: item.id, + mimeType: item.mimeType || 'application/octet-stream', + lastModified: Date.parse(item.modifiedTime), + size: item.size, + type: 'source', + }, + }; + }); +} diff --git a/src/contentproxy/sourcebus.js b/src/contentproxy/sourcebus.js index b2f3e74..cb1f017 100644 --- a/src/contentproxy/sourcebus.js +++ b/src/contentproxy/sourcebus.js @@ -15,6 +15,7 @@ import { html2md, TooManyImagesError } from '@adobe/helix-html2md'; import { Response } from '@adobe/fetch'; import { handleJSON } from './sourcebus-json.js'; import { handleFile } from './sourcebus-file.js'; +import { list } from './sourcebus-list.js'; import { validateSource } from './sourcebus-utils.js'; import { errorResponse } from '../support/utils.js'; import { error } from './errors.js'; @@ -141,5 +142,5 @@ export default { handle, handleJSON, handleFile, - list: () => { throw new Error('not implemented'); }, + list, }; From cf23616d6aff9801536eac03ce7513b94e8655c8 Mon Sep 17 00:00:00 2001 From: Tobias Bocanegra Date: Thu, 8 Jan 2026 11:48:22 +0100 Subject: [PATCH 09/11] feat: add list --- src/contentproxy/Forest.js | 18 +- src/contentproxy/google-list.js | 4 +- src/contentproxy/sourcebus-list.js | 199 +++++------------- src/contentproxy/sourcebus-utils.js | 4 +- .../fixtures/google-list-result.json | 30 +-- .../sourcebus/list-documents-result.json | 77 +++++++ .../fixtures/sourcebus/list-documents.xml | 53 +++++ test/contentproxy/google-list.test.js | 8 +- test/contentproxy/sourcebus-list.test.js | 105 +++++++++ test/contentproxy/sourcebus.test.js | 2 +- 10 files changed, 329 insertions(+), 171 deletions(-) create mode 100644 test/contentproxy/fixtures/sourcebus/list-documents-result.json create mode 100644 test/contentproxy/fixtures/sourcebus/list-documents.xml create mode 100644 test/contentproxy/sourcebus-list.test.js diff --git a/src/contentproxy/Forest.js b/src/contentproxy/Forest.js index 463cda1..64b64d6 100644 --- a/src/contentproxy/Forest.js +++ b/src/contentproxy/Forest.js @@ -90,11 +90,21 @@ export /* abstract */ class Forest { const folderPath = path.substring(0, idx); let items = folders[folderPath]; if (!items) { - // eslint-disable-next-line no-await-in-loop - items = await this.listFolder(rootItem, '', folderPath); - if (!items) { + try { + // eslint-disable-next-line no-await-in-loop + items = await this.listFolder(rootItem, '', folderPath); + if (!items) { + const infoPath = `${folderPath}/*`; + itemList.set(infoPath, { status: 404, path: infoPath }); + items = []; + } + } catch (e) { const infoPath = `${folderPath}/*`; - itemList.set(infoPath, { status: 404, path: infoPath }); + itemList.set(infoPath, { + status: e.$metadata.httpStatusCode ?? 500, + path: infoPath, + error: String(e), + }); items = []; } folders[folderPath] = items; diff --git a/src/contentproxy/google-list.js b/src/contentproxy/google-list.js index 1ecb2e0..a9b4f21 100644 --- a/src/contentproxy/google-list.js +++ b/src/contentproxy/google-list.js @@ -116,7 +116,7 @@ export class GoogleForest extends Forest { * @param {ProgressCallback} progressCB * @returns {Promise} the list of resources */ -export async function list(context, paths, progressCB) { +export async function list(context, info, paths, progressCB) { const { config: { content: { contentBusId, source } }, log } = context; const client = await context.getGoogleClient(contentBusId); @@ -133,7 +133,7 @@ export async function list(context, paths, progressCB) { source: { name: item.name, id: item.id, - mimeType: item.mimeType || 'application/octet-stream', + contentType: item.mimeType || 'application/octet-stream', lastModified: Date.parse(item.modifiedTime), size: item.size, type: 'gdrive', diff --git a/src/contentproxy/sourcebus-list.js b/src/contentproxy/sourcebus-list.js index 1fbc68a..1d1bd77 100644 --- a/src/contentproxy/sourcebus-list.js +++ b/src/contentproxy/sourcebus-list.js @@ -1,5 +1,5 @@ /* - * Copyright 2025 Adobe. All rights reserved. + * Copyright 2026 Adobe. All rights reserved. * This file is licensed to you under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. You may obtain a copy * of the License at https://www.apache.org/licenses/LICENSE-2.0 @@ -9,145 +9,62 @@ * OF ANY KIND, either express or implied. See the License for the specific language * governing permissions and limitations under the License. */ -import { sanitizeName, splitByExtension, editDistance } from '@adobe/helix-shared-string'; -import { GoogleClient } from '@adobe/helix-google-support'; +import { splitByExtension } from '@adobe/helix-shared-string'; +import { HelixStorage } from '@adobe/helix-shared-storage'; +import { basename, dirname } from 'path'; import { Forest } from './Forest.js'; -import {HelixStorage} from "@adobe/helix-shared-storage"; -import {getS3Key} from "../source/utils.js"; -import {Response} from "@adobe/fetch"; -import {createErrorResponse} from "../contentbus/utils.js"; -import {validateSource} from "./sourcebus-utils.js"; -import {errorResponse} from "../support/utils.js"; -import {error} from "./errors.js"; +import { error } from './errors.js'; +import { StatusCodeError } from '../support/StatusCodeError.js'; export class SourceForest extends Forest { - constructor(ctx) { + constructor(ctx, info) { super(ctx.log); this.ctx = ctx; + this.bucket = HelixStorage.fromContext(ctx).sourceBus(); + this.org = info.org; + this.site = info.site; } /** * List items below a root item. * @returns {Promise} */ - async listFolder(rootItem, rootPath, relPath) { - - - - - - - const bucket = HelixStorage.fromContext(context).sourceBus(); - const { org, site, rawPath: path } = info; - - try { - validateFolderPath(path); - const key = getS3Key(org, site, path); - const list = await bucket.list(key, { shallow: true, includePrefixes: true }); - - // Check the length of the raw filesList. This will include the - // directory marker files. So a directory with just a marker file - // is reported as empty, but without anything is reported as not found. - if (list.length === 0) { - return new Response('', { status: 404 }); + async listFolder(source, rootPath, relPath) { + const key = `${this.org}/${this.site}${relPath}`; + const listing = await this.bucket.list(key); + return listing.map((item) => { + /* + "key": "org/site/documents/index.html", + "lastModified": "2025-01-01T12:34:56.000Z", + "contentLength": 32768, + "contentType": "text/html", + "path": "/index.html" + */ + const path = `${rootPath}${relPath}${item.path}`; + const name = basename(item.path); + if (name === '.props') { + return null; } - - if (headRequest) { - return new Response('', { status: 200 }); - } - - const output = transformList(list); - const headers = { - 'Content-Type': 'application/json', + const [baseName, ext] = splitByExtension(path); // eg: /documents/index , .html + const ret = { + ...item, + path, + file: true, + resourcePath: path, + name: basename(path), + ext, }; - return new Response(JSON.stringify(output), { status: 200, headers }); - } catch (e) { - const opts = { e, log }; - opts.status = e.$metadata?.httpStatusCode; - return createErrorResponse(opts); - } - - - - - - const { log, client } = this; - const parentPath = `${rootPath}${relPath}`; - log.debug(`listing children for ${rootItem.id}:${parentPath}`); - let folderId = rootItem.id; - if (relPath) { - const hierarchy = await client.getItemsFromPath(rootItem.id, relPath, 'application/vnd.google-apps.folder'); - if (!hierarchy.length) { - return []; - } - folderId = hierarchy[0].id; - } - const opts = { - q: `'${folderId}' in parents and trashed=false`, - fields: 'nextPageToken, files(id, name, mimeType, modifiedTime, size)', - includeItemsFromAllDrives: true, - supportsAllDrives: true, - pageSize: 1000, - }; - - let itemList = []; - do { - // eslint-disable-next-line no-await-in-loop - const { data } = await client.drive.files.list(opts); - if (data.nextPageToken) { - opts.pageToken = data.nextPageToken; - } else { - opts.pageToken = null; - } - log.debug(`fetched ${data.files.length} items below ${folderId}. nextPageToken=${opts.pageToken ? '****' : 'null'}`); - itemList = itemList.concat(data.files); - } while (opts.pageToken); - - // sanitize names and filter out duplicates. - const map = new Map(); - for (const item of itemList) { - if (item.name === '.helix') { - // eslint-disable-next-line no-continue - continue; + if (name === 'index.html') { + ret.path = `${dirname(path)}/`; + ret.resourcePath = `${baseName}.md`; + ret.ext = '.md'; + } else if (ext === 'html') { + ret.path = baseName; + ret.resourcePath = `${baseName}.md`; + ret.ext = '.md'; } - const [itemName, ext] = splitByExtension(item.name); - const name = sanitizeName(itemName); - item.sanitizedName = ext ? `${name}.${ext}` : name; - item.file = true; - if (item.sanitizedName === 'index') { - item.path = `${parentPath}/`; - item.resourcePath = `${parentPath}/index.md`; - item.ext = '.md'; - } else if (item.mimeType === GoogleClient.TYPE_DOCUMENT) { - item.path = `${parentPath}/${name}`; - item.resourcePath = `${item.path}.md`; - item.ext = '.md'; - } else if (item.mimeType === GoogleClient.TYPE_SPREADSHEET) { - item.path = `${parentPath}/${name}.json`; - item.resourcePath = item.path; - item.ext = '.json'; - } else if (item.mimeType === 'application/vnd.google-apps.folder') { - delete item.file; - item.path = `${parentPath}/${name}`; - item.resourcePath = item.path; - } else if (ext === 'md') { - // ignore markdown files - // eslint-disable-next-line no-continue - continue; - } else { - item.path = `${parentPath}/${item.sanitizedName}`; - item.resourcePath = item.path; - item.ext = ext; - } - item.fuzzyDistance = editDistance(item.sanitizedName, itemName); - item.size = Number.parseInt(item.size, 10); - const existing = map.get(item.sanitizedName); - if (!existing || existing.fuzzyDistance > item.fuzzyDistance) { - map.set(item.sanitizedName, item); - } - } - log.info(`loaded ${map.size} children from ${rootItem.id}:${parentPath}`); - return Array.from(map.values()); + return ret; + }).filter((item) => !!item); } } /** @@ -157,13 +74,14 @@ export class SourceForest extends Forest { * * @type {import('./contentproxy.js').FetchList} * @param {import('../support/AdminContext').AdminContext} ctx context + * @param {PathInfo} info * @param {import('../support/RequestInfo').RequestInfo} info request info * @param {string[]} paths * @param {ProgressCallback} progressCB * @returns {Promise} the list of resources */ -export async function list(ctx, paths, progressCB) { - const { config: { content: { source } }, log } = ctx; +export async function list(ctx, info, paths, progressCB) { + const { config: { content: { source } } } = ctx; const sourceUrl = new URL(source.url); // extract org and site from url.pathname, format: https://api.aem.live//sites//source @@ -174,24 +92,20 @@ export async function list(ctx, paths, progressCB) { 'Source url must be in the format: https://api.aem.live//sites//source. Got: $1', sourceUrl.href, ); - throw new StatusCodeError(400, message); + throw new StatusCodeError(message, 400); } else { const [, org, site] = pathMatch; // eslint-disable-line prefer-destructuring if (org !== info.org || site !== info.site) { - ret.error = errorResponse(log, 400, error( + const { message } = error( 'Source bus is not allowed for org: $1, site: $2', - org, - site, - )); + info.org, + info.site, + ); + throw new StatusCodeError(message, 400); } } - // for now, only allow source bus from the same org and site - - - - - const forest = new SourceForest(ctx); + const forest = new SourceForest(ctx, info); const itemList = await forest.generate(source, paths, progressCB); return itemList.map((item) => { @@ -203,10 +117,9 @@ export async function list(ctx, paths, progressCB) { resourcePath: item.resourcePath, source: { name: item.name, - id: item.id, - mimeType: item.mimeType || 'application/octet-stream', - lastModified: Date.parse(item.modifiedTime), - size: item.size, + contentType: item.contentType, + lastModified: Date.parse(item.lastModified), + size: item.contentLength, type: 'source', }, }; diff --git a/src/contentproxy/sourcebus-utils.js b/src/contentproxy/sourcebus-utils.js index 3dc1eef..8ab786b 100644 --- a/src/contentproxy/sourcebus-utils.js +++ b/src/contentproxy/sourcebus-utils.js @@ -57,8 +57,8 @@ export async function validateSource(ctx, info, opts) { if (org !== info.org || site !== info.site) { ret.error = errorResponse(log, 400, error( 'Source bus is not allowed for org: $1, site: $2', - org, - site, + info.org, + info.site, )); } } diff --git a/test/contentproxy/fixtures/google-list-result.json b/test/contentproxy/fixtures/google-list-result.json index 0c21e5c..12b1623 100644 --- a/test/contentproxy/fixtures/google-list-result.json +++ b/test/contentproxy/fixtures/google-list-result.json @@ -5,7 +5,7 @@ "source": { "name": "index", "id": "5HfH4hk3VuCH9PF1BAaL4118Qw1biyEnPcSDXmwcLpzU", - "mimeType": "application/vnd.google-apps.document", + "contentType": "application/vnd.google-apps.document", "lastModified": 1698757554968, "size": 1024, "type": "gdrive" @@ -17,7 +17,7 @@ "source": { "name": "document", "id": "1HfH4hk3VuCH9PF1BAaL4118Qw1biyEnPcSDXmwcLpzU", - "mimeType": "application/vnd.google-apps.document", + "contentType": "application/vnd.google-apps.document", "lastModified": 1698757554968, "size": 1024, "type": "gdrive" @@ -29,7 +29,7 @@ "source": { "name": "Copy of helix-learnings-2022.pdf", "id": "1CrsvMdio9WXRS1XzkSUxJi2HZ-tPqCP9", - "mimeType": "application/pdf", + "contentType": "application/pdf", "lastModified": 1698754020000, "size": 3232437, "type": "gdrive" @@ -41,7 +41,7 @@ "source": { "name": "document-one", "id": "14WWbTHbCbbjlNxinNW-4CXyagpZWsKVAnSC5oyDAhoI", - "mimeType": "application/vnd.google-apps.document", + "contentType": "application/vnd.google-apps.document", "lastModified": 1698757728537, "size": 1024, "type": "gdrive" @@ -53,7 +53,7 @@ "source": { "name": "helix-learnings-2022.pdf", "id": "1_EKUoWcF9znjp1ZIkHePIzIiUuqM07bj", - "mimeType": "application/pdf", + "contentType": "application/pdf", "lastModified": 1698754020000, "size": 3232437, "type": "gdrive" @@ -65,7 +65,7 @@ "source": { "name": "sample.png", "id": "1-BljmlemrIiENu2p5hYlPj1i_AlXQxsX", - "mimeType": "image/png", + "contentType": "image/png", "lastModified": 1698754020000, "size": 3873, "type": "gdrive" @@ -77,7 +77,7 @@ "source": { "name": "sample.svg", "id": "1LwH2LFWRqI6cRRSS-Tl5Nb5imTSjESDl", - "mimeType": "image/svg+xml", + "contentType": "image/svg+xml", "lastModified": 1698754020000, "size": 1595, "type": "gdrive" @@ -89,7 +89,7 @@ "source": { "name": "simple phases", "id": "1yBOCRhnmw3WKTwW50A5ohk3V18tZ6MdPBcP0CGzSha4", - "mimeType": "application/vnd.google-apps.spreadsheet", + "contentType": "application/vnd.google-apps.spreadsheet", "lastModified": 1698757696990, "size": 1024, "type": "gdrive" @@ -101,7 +101,7 @@ "source": { "name": "sample.png", "id": "199eVwhIRttJCxGKo-B1IocE4apc3f1NT", - "mimeType": "image/png", + "contentType": "image/png", "lastModified": 1698754020000, "size": 3873, "type": "gdrive" @@ -113,7 +113,7 @@ "source": { "name": "sample.svg", "id": "1IQB86rZ5LhY7Crrt_lA5CPTvjHiHajwq", - "mimeType": "image/svg+xml", + "contentType": "image/svg+xml", "lastModified": 1698754020000, "size": 1595, "type": "gdrive" @@ -125,7 +125,7 @@ "source": { "name": "helix-learnings-2022.pdf", "id": "1xkum0nDsiWe1yx-szogxOy6gnePwkf_z", - "mimeType": "application/pdf", + "contentType": "application/pdf", "lastModified": 1698754020000, "size": 3232437, "type": "gdrive" @@ -141,7 +141,7 @@ "source": { "name": "sample.png", "id": "1vFvxScNLJDrleRZy-z25pYefRlcNKvFR", - "mimeType": "application/octet-stream", + "contentType": "application/octet-stream", "lastModified": 1698754020000, "size": 3873, "type": "gdrive" @@ -153,7 +153,7 @@ "source": { "name": "sample.svg", "id": "1JmSbTnOXM1t2zJiuokp3EBUtXMVreVOM", - "mimeType": "image/svg+xml", + "contentType": "image/svg+xml", "lastModified": 1698754020000, "size": 1595, "type": "gdrive" @@ -165,7 +165,7 @@ "source": { "name": "test", "id": "1FREVtyDzwkxCD0l_PFAM5yg5AoJWnyeMY6T1Nm8KTwg", - "mimeType": "application/vnd.google-apps.document", + "contentType": "application/vnd.google-apps.document", "lastModified": 1698757587061, "size": 1024, "type": "gdrive" @@ -177,7 +177,7 @@ "source": { "name": "unsafe.svg", "id": "1YvrkuFYscNdR87hTvt1V6X-gh4loCJPB", - "mimeType": "image/svg+xml", + "contentType": "image/svg+xml", "lastModified": 1698754020000, "size": 1632, "type": "gdrive" diff --git a/test/contentproxy/fixtures/sourcebus/list-documents-result.json b/test/contentproxy/fixtures/sourcebus/list-documents-result.json new file mode 100644 index 0000000..e222495 --- /dev/null +++ b/test/contentproxy/fixtures/sourcebus/list-documents-result.json @@ -0,0 +1,77 @@ +[ + { + "error": "Unknown: UnknownError", + "path": "/blog/*", + "status": 500 + }, + { + "path": "/blog/post1", + "status": 404 + }, + { + "path": "/documents/", + "resourcePath": "/documents/index.md", + "source": { + "name": "index.html", + "contentType": "text/html", + "lastModified": 1735734896000, + "size": 32768, + "type": "source" + } + }, + { + "path": "/documents/document", + "resourcePath": "/documents/document.md", + "source": { + "name": "document.html", + "contentType": "text/html", + "lastModified": 1735734896000, + "size": 32768, + "type": "source" + } + }, + { + "path": "/documents/folder/data.json", + "resourcePath": "/documents/folder/data.json", + "source": { + "name": "data.json", + "contentType": "application/json", + "lastModified": 1640912461000, + "size": 123, + "type": "source" + } + }, + { + "path": "/documents/folder/sample.png", + "resourcePath": "/documents/folder/sample.png", + "source": { + "name": "sample.png", + "contentType": "image/png", + "lastModified": 1640912461000, + "size": 123, + "type": "source" + } + }, + { + "path": "/documents/news/", + "resourcePath": "/documents/news/index.md", + "source": { + "name": "index.html", + "contentType": "text/html", + "lastModified": 1735734896000, + "size": 32768, + "type": "source" + } + }, + { + "path": "/documents/report.pdf", + "resourcePath": "/documents/report.pdf", + "source": { + "name": "report.pdf", + "contentType": "application/pdf", + "lastModified": 978310861000, + "size": 88888, + "type": "source" + } + } +] diff --git a/test/contentproxy/fixtures/sourcebus/list-documents.xml b/test/contentproxy/fixtures/sourcebus/list-documents.xml new file mode 100644 index 0000000..ee68377 --- /dev/null +++ b/test/contentproxy/fixtures/sourcebus/list-documents.xml @@ -0,0 +1,53 @@ + + helix-source-bus + org/site/documents/ + + 1000 + false + + org/site/documents/index.html + 2025-01-01T12:34:56.000Z + 32768 + + + org/site/documents/document.html + 2025-01-01T12:34:56.000Z + 32768 + + + org/site/documents/news/index.html + 2025-01-01T12:34:56.000Z + 32768 + + + org/site/documents/news/.props + 2021-12-31T01:01:01.001Z + 3 + + + org/site/documents/report.pdf + 2001-01-01T01:01:01.001Z + 88888 + + + org/site/documents/folder/.props + 2021-12-31T01:01:01.001Z + 3 + + + org/site/documents/folder/data.json + 2021-12-31T01:01:01.001Z + 123 + + + org/site/documents/folder/sample.png + 2021-12-31T01:01:01.001Z + 123 + + + org/site/documents/news/ + + + org/site/documents/folder/ + + diff --git a/test/contentproxy/google-list.test.js b/test/contentproxy/google-list.test.js index 0d43c04..6222689 100644 --- a/test/contentproxy/google-list.test.js +++ b/test/contentproxy/google-list.test.js @@ -84,8 +84,8 @@ describe('Google Integration Tests (list)', () => { 'content-type': 'application/json', }); - const { context } = setupTest('/'); - const result = await list(context, ['/documents/*', '/documents/not-found']); + const { context, info } = setupTest('/'); + const result = await list(context, info, ['/documents/*', '/documents/not-found']); assert.deepStrictEqual(result, JSON.parse(await readFile(specPath('google-list-result.json')))); }); @@ -95,8 +95,8 @@ describe('Google Integration Tests (list)', () => { .user() .folders([]); - const { context } = setupTest('/'); - const result = await list(context, ['/documents/*']); + const { context, info } = setupTest('/'); + const result = await list(context, info, ['/documents/*']); assert.deepStrictEqual(result, []); }); diff --git a/test/contentproxy/sourcebus-list.test.js b/test/contentproxy/sourcebus-list.test.js new file mode 100644 index 0000000..4f8c243 --- /dev/null +++ b/test/contentproxy/sourcebus-list.test.js @@ -0,0 +1,105 @@ +/* + * Copyright 2025 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +/* eslint-env mocha */ +import assert from 'assert'; +import { resolve } from 'path'; +import { readFile } from 'fs/promises'; +import { list } from '../../src/contentproxy/sourcebus-list.js'; +import { + Nock, SITE_CONFIG, createContext, createInfo, +} from '../utils.js'; +import { StatusCodeError } from '../../src/support/StatusCodeError.js'; + +const SITE_MUP_CONFIG = (url = 'https://api.aem.live/org/sites/site/source') => ({ + ...SITE_CONFIG, + content: { + ...SITE_CONFIG.content, + source: { + type: 'markup', + url, + }, + }, +}); + +function specPath(spec) { + return resolve(__testdir, 'contentproxy', 'fixtures', spec); +} + +describe('Source Bus Content Proxy Tests (list)', () => { + /** @type {import('../utils.js').NockEnv} */ + let nock; + + beforeEach(() => { + nock = new Nock().env(); + }); + + afterEach(() => { + nock.done(); + }); + + function setupTest(suffix = '/org/sites/site/contentproxy/', { + config = SITE_MUP_CONFIG(), + } = {}) { + const context = createContext(suffix, { + attributes: { config }, + }); + const info = createInfo(suffix, { + 'x-workbook-session-id': 'test-session-id', + }).withCode('owner', 'repo'); + return { context, info }; + } + + it('Retrieves tree list from sourcebus', async () => { + nock.source() + .get('/') + .query({ + 'list-type': '2', + prefix: 'org/site/documents', + }) + .replyWithFile(200, resolve(__testdir, 'contentproxy/fixtures/sourcebus/list-documents.xml'), { + 'last-modified': 'Thu, 11 Dec 2025 12:00:00 GMT', + 'content-type': 'text/xml', + }) + .get('/') + .query({ + 'list-type': '2', + prefix: 'org/site/blog', + }) + .reply(500); + + const { context, info } = setupTest(); + const result = await list(context, info, [ + '/documents/*', + '/blog/post1', + ]); + + assert.deepStrictEqual(result, JSON.parse(await readFile(specPath('sourcebus/list-documents-result.json')))); + }); + + it('Rejects list if source.url has the correct format', async () => { + const { context, info } = setupTest(undefined, { + config: { + ...SITE_MUP_CONFIG('https://api.aem.live/org/sites/status'), + }, + }); + await assert.rejects(list(context, info, ['/documents/*']), new StatusCodeError('Source url must be in the format: https://api.aem.live//sites//source. Got: https://api.aem.live/org/sites/status', 400)); + }); + it('Rejects list if source.url is on wrong org/site', async () => { + const { context, info } = setupTest('/org/sites/othersite/contentproxy/', { + config: { + ...SITE_MUP_CONFIG('https://api.aem.live/org/sites/site/source'), + }, + }); + await assert.rejects(list(context, info, ['/documents/*']), new StatusCodeError('Source bus is not allowed for org: org, site: othersite', 400)); + }); +}); diff --git a/test/contentproxy/sourcebus.test.js b/test/contentproxy/sourcebus.test.js index 509a4fd..a55e9a9 100644 --- a/test/contentproxy/sourcebus.test.js +++ b/test/contentproxy/sourcebus.test.js @@ -170,7 +170,7 @@ describe('Source Bus Content Proxy Tests', () => { assert.deepStrictEqual(response.headers.plain(), { 'content-type': 'text/plain; charset=utf-8', 'cache-control': 'no-store, private, must-revalidate', - 'x-error': 'Source bus is not allowed for org: org, site: another', + 'x-error': 'Source bus is not allowed for org: org, site: site', }); }); From 0cd6d430e87f4d376843b18677f91a5ee6668db4 Mon Sep 17 00:00:00 2001 From: Tobias Bocanegra Date: Thu, 8 Jan 2026 11:49:52 +0100 Subject: [PATCH 10/11] chore: fix typo --- src/contentproxy/sourcebus.js | 2 +- test/contentproxy/sourcebus-file.test.js | 2 +- test/contentproxy/sourcebus-json.test.js | 2 +- test/contentproxy/sourcebus.test.js | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/contentproxy/sourcebus.js b/src/contentproxy/sourcebus.js index cb1f017..d48c8af 100644 --- a/src/contentproxy/sourcebus.js +++ b/src/contentproxy/sourcebus.js @@ -66,7 +66,7 @@ async function handle(ctx, info, opts) { } const { - MEDIAHANDLER_NOCACHHE: noCache, + MEDIAHANDLER_NOCACHE: noCache, CLOUDFLARE_ACCOUNT_ID: r2AccountId, CLOUDFLARE_R2_ACCESS_KEY_ID: r2AccessKeyId, CLOUDFLARE_R2_SECRET_ACCESS_KEY: r2SecretAccessKey, diff --git a/test/contentproxy/sourcebus-file.test.js b/test/contentproxy/sourcebus-file.test.js index a27fa85..714d1cb 100644 --- a/test/contentproxy/sourcebus-file.test.js +++ b/test/contentproxy/sourcebus-file.test.js @@ -66,7 +66,7 @@ describe('Source Bus Content Proxy Tests (JSON)', () => { runtime: { region: 'us-east-1' }, env: { HLX_CONFIG_SERVICE_TOKEN: 'token', - MEDIAHANDLER_NOCACHHE: 'true', + MEDIAHANDLER_NOCACHE: 'true', }, }; return { request, context }; diff --git a/test/contentproxy/sourcebus-json.test.js b/test/contentproxy/sourcebus-json.test.js index 646f513..2e54a21 100644 --- a/test/contentproxy/sourcebus-json.test.js +++ b/test/contentproxy/sourcebus-json.test.js @@ -66,7 +66,7 @@ describe('Source Bus Content Proxy Tests (JSON)', () => { runtime: { region: 'us-east-1' }, env: { HLX_CONFIG_SERVICE_TOKEN: 'token', - MEDIAHANDLER_NOCACHHE: 'true', + MEDIAHANDLER_NOCACHE: 'true', }, }; return { request, context }; diff --git a/test/contentproxy/sourcebus.test.js b/test/contentproxy/sourcebus.test.js index a55e9a9..af5c38e 100644 --- a/test/contentproxy/sourcebus.test.js +++ b/test/contentproxy/sourcebus.test.js @@ -66,7 +66,7 @@ describe('Source Bus Content Proxy Tests', () => { runtime: { region: 'us-east-1' }, env: { HLX_CONFIG_SERVICE_TOKEN: 'token', - MEDIAHANDLER_NOCACHHE: 'true', + MEDIAHANDLER_NOCACHE: 'true', }, }; return { request, context }; From cb0bd3be150dc6641b7751f853d84ea8560ccacb Mon Sep 17 00:00:00 2001 From: Tobias Bocanegra Date: Thu, 8 Jan 2026 14:05:31 +0100 Subject: [PATCH 11/11] chore: cleanup --- src/contentproxy/markup-json.js | 19 +++++-------------- src/contentproxy/sourcebus-json.js | 14 +------------- src/contentproxy/sourcebus-list.js | 28 +++++----------------------- src/contentproxy/sourcebus-utils.js | 19 +++++++++---------- src/contentproxy/utils.js | 19 +++++++++++++++++++ 5 files changed, 39 insertions(+), 60 deletions(-) diff --git a/src/contentproxy/markup-json.js b/src/contentproxy/markup-json.js index 10efe72..5f222fd 100644 --- a/src/contentproxy/markup-json.js +++ b/src/contentproxy/markup-json.js @@ -13,23 +13,14 @@ import { AbortError, Response } from '@adobe/fetch'; import { HelixStorage } from '@adobe/helix-shared-storage'; import { errorResponse } from '../support/utils.js'; import { - getContentSourceHeaders, assertValidSheetJSON, computeSourceUrl, - updateMarkupSourceInfo, addLastModified, + addLastModified, + computeSourceUrl, + getContentSourceHeaders, + parseSheetJSON, + updateMarkupSourceInfo, } from './utils.js'; import { error } from './errors.js'; -function parseSheetJSON(data) { - let json; - try { - json = JSON.parse(data); - } catch { - throw Error('invalid sheet json; failed to parse'); - } - - assertValidSheetJSON(json); - return json; -} - /** * Fetch timeout for markup source. */ diff --git a/src/contentproxy/sourcebus-json.js b/src/contentproxy/sourcebus-json.js index 79fa92e..408b7a9 100644 --- a/src/contentproxy/sourcebus-json.js +++ b/src/contentproxy/sourcebus-json.js @@ -12,22 +12,10 @@ import { Response } from '@adobe/fetch'; import { HelixStorage } from '@adobe/helix-shared-storage'; import { errorResponse } from '../support/utils.js'; -import { assertValidSheetJSON } from './utils.js'; +import { parseSheetJSON } from './utils.js'; import { validateSource } from './sourcebus-utils.js'; import { error } from './errors.js'; -function parseSheetJSON(data) { - let json; - try { - json = JSON.parse(data); - } catch { - throw Error('invalid sheet json; failed to parse'); - } - - assertValidSheetJSON(json); - return json; -} - /** * Fetches a JSON as sheet/multisheet from the source bus * diff --git a/src/contentproxy/sourcebus-list.js b/src/contentproxy/sourcebus-list.js index 1d1bd77..c3ba41a 100644 --- a/src/contentproxy/sourcebus-list.js +++ b/src/contentproxy/sourcebus-list.js @@ -13,8 +13,8 @@ import { splitByExtension } from '@adobe/helix-shared-string'; import { HelixStorage } from '@adobe/helix-shared-storage'; import { basename, dirname } from 'path'; import { Forest } from './Forest.js'; -import { error } from './errors.js'; import { StatusCodeError } from '../support/StatusCodeError.js'; +import { validateSource } from './sourcebus-utils.js'; export class SourceForest extends Forest { constructor(ctx, info) { @@ -51,7 +51,7 @@ export class SourceForest extends Forest { path, file: true, resourcePath: path, - name: basename(path), + name, ext, }; if (name === 'index.html') { @@ -82,27 +82,9 @@ export class SourceForest extends Forest { */ export async function list(ctx, info, paths, progressCB) { const { config: { content: { source } } } = ctx; - const sourceUrl = new URL(source.url); - - // extract org and site from url.pathname, format: https://api.aem.live//sites//source - // e.g. /adobe/sites/foo/source - const pathMatch = sourceUrl.pathname.match(/^\/([^/]+)\/sites\/([^/]+)\/source$/); - if (!pathMatch) { - const { message } = error( - 'Source url must be in the format: https://api.aem.live//sites//source. Got: $1', - sourceUrl.href, - ); - throw new StatusCodeError(message, 400); - } else { - const [, org, site] = pathMatch; // eslint-disable-line prefer-destructuring - if (org !== info.org || site !== info.site) { - const { message } = error( - 'Source bus is not allowed for org: $1, site: $2', - info.org, - info.site, - ); - throw new StatusCodeError(message, 400); - } + const { error } = await validateSource(ctx, info); + if (error) { + throw new StatusCodeError(error.headers.get('x-error'), error.status); } const forest = new SourceForest(ctx, info); diff --git a/src/contentproxy/sourcebus-utils.js b/src/contentproxy/sourcebus-utils.js index 8ab786b..e570f6f 100644 --- a/src/contentproxy/sourcebus-utils.js +++ b/src/contentproxy/sourcebus-utils.js @@ -32,7 +32,7 @@ import { error } from './errors.js'; * @param {number} [opts.fetchTimeout] fetch timeout * @returns {Promise} the validation result */ -export async function validateSource(ctx, info, opts) { +export function validateSource(ctx, info, opts) { const { config: { content }, log } = ctx; const source = opts?.source ?? content.source; const sourceUrl = new URL(source.url); @@ -46,20 +46,19 @@ export async function validateSource(ctx, info, opts) { // extract org and site from url.pathname, format: https://api.aem.live//sites//source // e.g. /adobe/sites/foo/source - const pathMatch = sourceUrl.pathname.match(/^\/([^/]+)\/sites\/([^/]+)\/source$/); - if (!pathMatch) { - ret.error = errorResponse(log, 400, error( - 'Source url must be in the format: https://api.aem.live//sites//source. Got: $1', - sourceUrl.href, - )); - } else { - const [, org, site] = pathMatch; // eslint-disable-line prefer-destructuring - if (org !== info.org || site !== info.site) { + const { org, site } = sourceUrl.pathname.match(/^\/(?[^/]+)\/sites\/(?[^/]+)\/source$/)?.groups ?? {}; + if (org !== info.org || site !== info.site) { + if (org && site) { ret.error = errorResponse(log, 400, error( 'Source bus is not allowed for org: $1, site: $2', info.org, info.site, )); + } else { + ret.error = errorResponse(log, 400, error( + 'Source url must be in the format: https://api.aem.live//sites//source. Got: $1', + sourceUrl.href, + )); } } diff --git a/src/contentproxy/utils.js b/src/contentproxy/utils.js index 0d94d10..9878a38 100644 --- a/src/contentproxy/utils.js +++ b/src/contentproxy/utils.js @@ -260,3 +260,22 @@ export function addLastModified(headers, value) { } return headers; } + +/** + * Parses a JSON string and validates it as a sheet JSON. + * Throws an error if parsing fails or the JSON is invalid. + * + * @param {string} data - The JSON string to parse. + * @returns {object} The validated sheet JSON object. + */ +export function parseSheetJSON(data) { + let json; + try { + json = JSON.parse(data); + } catch { + throw Error('invalid sheet json; failed to parse'); + } + + assertValidSheetJSON(json); + return json; +}