diff --git a/addon/components/docs-header/index.js b/addon/components/docs-header/index.js index d36fffc85..d709f980d 100644 --- a/addon/components/docs-header/index.js +++ b/addon/components/docs-header/index.js @@ -87,7 +87,9 @@ export default class DocsHeader extends Component { @action didVisitPage() { this.query = null; - let search = document.querySelector('[data-search-box-input]'); - search.blur(); + if (typeof document !== 'undefined') { + let search = document.querySelector('[data-search-box-input]'); + search?.blur(); + } } } diff --git a/addon/components/docs-header/search-box/index.js b/addon/components/docs-header/search-box/index.js index 423216975..786d1e138 100644 --- a/addon/components/docs-header/search-box/index.js +++ b/addon/components/docs-header/search-box/index.js @@ -22,6 +22,8 @@ export default class DocsHeaderSearchBox extends Component { @action focusSearch() { + if (typeof document === 'undefined') return; + if (!formElementHasFocus()) { this.element.querySelector('input').focus(); } diff --git a/addon/components/docs-modal-dialog.js b/addon/components/docs-modal-dialog.js index 66eb02234..105991929 100644 --- a/addon/components/docs-modal-dialog.js +++ b/addon/components/docs-modal-dialog.js @@ -7,6 +7,10 @@ export default class DocsModalDialog extends ModalDialog { super.init(...arguments); const config = getOwner(this).resolveRegistration('config:environment'); - this.set('renderInPlace', config.environment === 'test'); + let fastboot = getOwner(this).lookup('service:fastboot'); + this.set( + 'renderInPlace', + config.environment === 'test' || fastboot?.isFastBoot, + ); } } diff --git a/addon/components/docs-viewer/x-main/index.js b/addon/components/docs-viewer/x-main/index.js index f1e2dd21e..d5eee491c 100644 --- a/addon/components/docs-viewer/x-main/index.js +++ b/addon/components/docs-viewer/x-main/index.js @@ -21,6 +21,8 @@ export default class XMain extends Component { @action setupElement(element) { + if (typeof MutationObserver === 'undefined') return; + let target = element.querySelector('[data-current-page-index-target]'); this._mutationObserver = new MutationObserver( diff --git a/addon/keyboard-config.js b/addon/keyboard-config.js index 58d90347d..491af442b 100644 --- a/addon/keyboard-config.js +++ b/addon/keyboard-config.js @@ -9,6 +9,8 @@ const TAGNAMES_THAT_WHEN_FOCUSED_PREVENT_KEYBOARD_SHORTCUTS = [ @hide */ export function formElementHasFocus() { + if (typeof document === 'undefined') return false; + return TAGNAMES_THAT_WHEN_FOCUSED_PREVENT_KEYBOARD_SHORTCUTS.includes( document.activeElement.tagName, ); diff --git a/addon/services/docs-search.js b/addon/services/docs-search.js index f629b7552..fb20c9fac 100644 --- a/addon/services/docs-search.js +++ b/addon/services/docs-search.js @@ -1,4 +1,5 @@ import Service from '@ember/service'; +import { getOwner } from '@ember/application'; import lunr from 'lunr'; import { task } from 'ember-concurrency'; import { @@ -12,6 +13,7 @@ export default class DocsSearch extends Service { async search(phrase) { const { searchTokenSeparator } = getAddonDocsConfig(this); let { index, documents } = await this.loadSearchIndex(); + if (!index) return []; let words = phrase.toLowerCase().split(new RegExp(searchTokenSeparator)); let results = index.query((query) => { // In the future we could boost results based on the field they come from @@ -91,6 +93,12 @@ export default class DocsSearch extends Service { _loadSearchIndex = task({ enqueue: true }, async () => { if (!this._searchIndex) { + let fastboot = getOwner(this).lookup('service:fastboot'); + if (fastboot?.isFastBoot) { + this._searchIndex = { index: null, documents: {} }; + return this._searchIndex; + } + let response = await fetch(this._indexURL); let json = await response.json(); diff --git a/addon/services/docs-store.js b/addon/services/docs-store.js index f90ca998f..6d3b1a675 100644 --- a/addon/services/docs-store.js +++ b/addon/services/docs-store.js @@ -1,4 +1,6 @@ +/* global FastBoot */ import Service from '@ember/service'; +import { getOwner } from '@ember/application'; import { tracked } from '@glimmer/tracking'; import { getRootURL } from 'ember-cli-addon-docs/-private/config'; @@ -52,11 +54,35 @@ export default class DocsStoreService extends Service { } async _fetchProject(id) { - let namespace = `${getRootURL(this).replace(/\/$/, '')}/docs`; - let url = `${namespace}/${id}.json`; - - let response = await fetch(url); - let payload = await response.json(); + let payload; + + let fastboot = getOwner(this).lookup('service:fastboot'); + if (fastboot?.isFastBoot) { + // In FastBoot, read the docs JSON directly from the dist directory + // using FastBoot.distPath. This works during both prember builds and + // ember serve with fastboot, without needing an HTTP request. + let fs = FastBoot.require('fs'); + let path = FastBoot.require('path'); + let filePath = path.join(FastBoot.distPath, 'docs', `${id}.json`); + payload = JSON.parse(fs.readFileSync(filePath, 'utf8')); + } else { + let namespace = `${getRootURL(this).replace(/\/$/, '')}/docs`; + let url = `${namespace}/${id}.json`; + let response; + try { + response = await fetch(url); + } catch (e) { + throw new Error( + `Network error while fetching ${url}: ${e && e.message}`, + ); + } + if (!response.ok) { + throw new Error( + `Request to ${url} failed with status ${response.status}`, + ); + } + payload = await response.json(); + } this._loadPayload(payload); @@ -67,7 +93,11 @@ export default class DocsStoreService extends Service { let allRecords = []; // Collect data (can be single or array) - let dataItems = Array.isArray(payload.data) ? payload.data : [payload.data]; + let dataItems = Array.isArray(payload.data) + ? payload.data + : payload.data + ? [payload.data] + : []; allRecords.push(...dataItems); // Collect included diff --git a/addon/services/project-version.js b/addon/services/project-version.js index 49ecfdef0..50020705f 100644 --- a/addon/services/project-version.js +++ b/addon/services/project-version.js @@ -1,4 +1,5 @@ import Service from '@ember/service'; +import { getOwner } from '@ember/application'; import { task } from 'ember-concurrency'; import { tracked } from '@glimmer/tracking'; import { @@ -12,6 +13,18 @@ export default class ProjectVersionService extends Service { @addonDocsConfig config; _loadAvailableVersions = task(async () => { + let fastboot = getOwner(this).lookup('service:fastboot'); + if (fastboot?.isFastBoot) { + this.versions = [ + { + ...this.currentVersion, + truncatedSha: this.currentVersion.sha?.substr(0, 5) || '', + key: this.config.latestVersionName, + }, + ]; + return; + } + let response = await fetch(`${this.root}versions.json`); let json; if (response.ok) { @@ -32,6 +45,7 @@ export default class ProjectVersionService extends Service { }); redirectTo(version) { + if (typeof window === 'undefined') return; window.location.href = `${this.root}${version.path}`; } diff --git a/blueprints/ember-cli-addon-docs/index.js b/blueprints/ember-cli-addon-docs/index.js index b2f40b658..49da761d8 100644 --- a/blueprints/ember-cli-addon-docs/index.js +++ b/blueprints/ember-cli-addon-docs/index.js @@ -19,34 +19,13 @@ module.exports = { 'ember-cli-deploy-build', 'ember-cli-deploy-git', 'ember-cli-deploy-git-ci', + 'ember-cli-fastboot', + 'prember', ], }); }, - afterInstall(options) { - let configPath = require.resolve(this.project.configPath()); - let configContents = fs.readFileSync(configPath, 'utf-8'); - - if (configContents.indexOf('ADDON_DOCS_ROOT_URL') === -1) { - configContents = configContents.replace( - /([ \t]+)if \(environment === 'production'\) {/, - [ - '$&', - '$1 // Allow ember-cli-addon-docs to update the rootURL in compiled assets', - "$1 ENV.rootURL = '/ADDON_DOCS_ROOT_URL/';", - ].join('\n'), - ); - - if (configContents.indexOf('ADDON_DOCS_ROOT_URL') === -1) { - this.ui.writeWarnLine( - `Unable to update rootURL configuration. You should update ${configPath} so that your rootURL is ` + - `the string '/ADDON_DOCS_ROOT_URL/' in production.`, - ); - } - } - - fs.writeFileSync(configPath, configContents, 'utf-8'); - + afterInstall() { if (fs.existsSync('.npmignore')) { this.insertIntoFile('.npmignore', '/config/addon-docs.js'); } diff --git a/index.js b/index.js index e02743e6f..5ab2f3bb9 100644 --- a/index.js +++ b/index.js @@ -130,6 +130,13 @@ module.exports = { } } + // Set up prember for static site generation if not already configured + if (!includer.options.prember) { + includer.options.prember = { + urls: require('./lib/prember-urls'), + }; + } + includer.options.includeFileExtensionInSnippetNames = includer.options.includeFileExtensionInSnippetNames || false; if (!includer.options.snippetSearchPaths) { diff --git a/lib/prember-urls.js b/lib/prember-urls.js new file mode 100644 index 000000000..bf5158ec8 --- /dev/null +++ b/lib/prember-urls.js @@ -0,0 +1,94 @@ +'use strict'; + +const fs = require('fs'); +const path = require('path'); + +/** + * Prember URL enumeration for ember-cli-addon-docs. + * + * Automatically discovers all documentation pages by reading + * the generated docs JSON and search index from the build output. + * + * Usage in ember-cli-build.js: + * + * const app = new EmberAddon(defaults, { + * prember: { + * urls: require('ember-cli-addon-docs/lib/prember-urls'), + * }, + * }); + * + * @param {Object} options + * @param {string} options.distDir - Path to the built dist directory + * @returns {string[]} Array of URLs to pre-render + */ +module.exports = function premberUrls({ distDir }) { + let urls = new Set(['/']); + + // Discover guide/template pages and API pages from the search index. + // The search index contains all indexed documents with their route info. + let searchIndexPath = path.join( + distDir, + 'ember-cli-addon-docs', + 'search-index.json', + ); + + if (fs.existsSync(searchIndexPath)) { + let searchIndex = JSON.parse(fs.readFileSync(searchIndexPath, 'utf8')); + + for (let doc of Object.values(searchIndex.documents || {})) { + if (doc.type === 'template' && doc.route) { + // Skip internal/non-page routes + if ( + doc.route === 'application' || + doc.route === 'not-found' || + doc.route.startsWith('templates.') || + doc.route.startsWith('pods.') + ) { + continue; + } + + let routePath = doc.route.replace(/\./g, '/').replace(/\/index$/, ''); + if (routePath === 'index') routePath = ''; + urls.add('/' + routePath); + } + } + } + + // Discover API pages from the main project's docs JSON navigationIndex. + // We read all JSON files in docs/ and use the project ID to build URLs. + // The main project maps to /docs/api/, additional projects would need + // custom URL mapping from the consuming app. + let docsDir = path.join(distDir, 'docs'); + + if (fs.existsSync(docsDir)) { + let files = fs + .readdirSync(docsDir) + .filter((f) => f.endsWith('.json')) + .sort(); + + for (let i = 0; i < files.length; i++) { + let docsJson = JSON.parse( + fs.readFileSync(path.join(docsDir, files[i]), 'utf8'), + ); + let projectData = Array.isArray(docsJson.data) + ? docsJson.data[0] + : docsJson.data; + let navIndex = projectData?.attributes?.navigationIndex || []; + + // Only generate /docs/api/ URLs for the first (alphabetically) + // project. Additional projects have their own route prefixes + // that we can't determine automatically. + if (files.length === 1 || i === 0) { + urls.add('/docs'); + + for (let section of navIndex) { + for (let item of section.items) { + urls.add(`/docs/api/${item.path}`); + } + } + } + } + } + + return [...urls]; +}; diff --git a/lib/utils/find-and-replace-in-directory.js b/lib/utils/find-and-replace-in-directory.js index 78daa633d..ed067af87 100644 --- a/lib/utils/find-and-replace-in-directory.js +++ b/lib/utils/find-and-replace-in-directory.js @@ -5,21 +5,50 @@ const fs = require('fs-extra'); const path = require('path'); -function replaceAddonDocsRootURL(contents, addonDocsRootURL, encodedVersion) { - return contents - .replace('%2FADDON_DOCS_ROOT_URL%2F', encodeURIComponent(addonDocsRootURL)) - .replace(/\/?ADDON_DOCS_ROOT_URL\/?/g, addonDocsRootURL) - .replace(/%22ADDON_DOCS_DEPLOY_VERSION%22/g, encodedVersion); +/** + * Replaces rootURL and deploy version tokens in file contents. + * + * The app is built with rootURL = '/' and deployVersion = 'ADDON_DOCS_DEPLOY_VERSION'. + * At deploy time, these are rewritten to the real values. + */ +function replaceDeployTokens(contents, addonDocsRootURL, encodedVersion) { + return ( + contents + // Replace rootURL in the URI-encoded config meta tag: "rootURL":"/" → "rootURL":"/my-addon/versions/main/" + .replace( + /%22rootURL%22%3A%22%2F%22/g, + `%22rootURL%22%3A%22${encodeURIComponent(addonDocsRootURL)}%22`, + ) + // Replace asset paths: src="/assets/ → src="/my-addon/versions/main/assets/ + // and href="/assets/ → href="/my-addon/versions/main/assets/ + .replace(/((?:src|href)=")\/assets\//g, `$1${addonDocsRootURL}assets/`) + // Replace webpack public path: ="\/assets\/" or ="/assets/" + .replace(/="\/assets\/"/g, `="${addonDocsRootURL}assets/"`) + // Replace bare /assets/ references in JS (webpack chunk loading etc.) + .replace(/(["`])\/assets\//g, `$1${addonDocsRootURL}assets/`) + // Handle the legacy ADDON_DOCS_ROOT_URL token for backward compatibility + // with consuming apps that haven't updated their config yet + .replace( + '%2FADDON_DOCS_ROOT_URL%2F', + encodeURIComponent(addonDocsRootURL), + ) + .replace(/\/?ADDON_DOCS_ROOT_URL\/?/g, addonDocsRootURL) + // Replace deploy version token + .replace(/%22ADDON_DOCS_DEPLOY_VERSION%22/g, encodedVersion) + ); } function processFile(filePath, addonDocsRootURL, encodedVersion) { const contents = fs.readFileSync(filePath, 'utf-8'); - - // Write the updated content to the file - fs.writeFileSync( - filePath, - replaceAddonDocsRootURL(contents, addonDocsRootURL, encodedVersion), + const updated = replaceDeployTokens( + contents, + addonDocsRootURL, + encodedVersion, ); + + if (updated !== contents) { + fs.writeFileSync(filePath, updated); + } } function findAndReplaceInDirectory( @@ -37,10 +66,8 @@ function findAndReplaceInDirectory( const fullPath = path.join(directory, entry.name); if (entry.isDirectory()) { - // Recursively process subdirectories findAndReplaceInDirectory(fullPath, addonDocsRootURL, encodedVersion); } else if (entry.isFile()) { - // Process files processFile(fullPath, addonDocsRootURL, encodedVersion); } }); @@ -49,5 +76,5 @@ function findAndReplaceInDirectory( module.exports = { findAndReplaceInDirectory, - replaceAddonDocsRootURL, + replaceDeployTokens, }; diff --git a/package.json b/package.json index 1c3ded33f..94cf3d618 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,10 @@ "ember-addon", "ember-cli-deploy-plugin" ], + "fastbootDependencies": [ + "fs", + "path" + ], "repository": "https://github.com/ember-learn/ember-cli-addon-docs", "license": "MIT", "author": "", @@ -122,6 +126,7 @@ "ember-cli-deploy-build": "^3.0.0", "ember-cli-deploy-git": "^1.3.4", "ember-cli-deploy-git-ci": "^1.0.1", + "ember-cli-fastboot": "^4.1.5", "ember-cli-inject-live-reload": "^2.1.0", "ember-cli-sri": "^2.1.1", "ember-cli-terser": "^4.0.2", @@ -140,8 +145,9 @@ "eslint-plugin-prettier": "^5.5.4", "eslint-plugin-qunit": "^8.2.5", "loader.js": "^4.7.0", - "pretender": "^3.4.7", "mocha": "^11.7.5", + "prember": "^2.0.0", + "pretender": "^3.4.7", "prettier": "^3.7.2", "qunit": "^2.24.2", "qunit-dom": "^3.5.0", @@ -153,7 +159,9 @@ }, "peerDependencies": { "@ember/test-waiters": "^3.1.0 || ^4.0.0", - "ember-source": ">= 4.0.0" + "ember-cli-fastboot": ">= 4.0.0", + "ember-source": ">= 4.0.0", + "prember": ">= 2.0.0" }, "pnpm": { "overrides": { @@ -179,7 +187,8 @@ "webpack": "$webpack" }, "onlyBuiltDependencies": [ - "core-js" + "@parcel/watcher", + "ember-modal-dialog" ] }, "engines": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ec788d86d..b8d15261c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -301,6 +301,9 @@ importers: ember-cli-deploy-git-ci: specifier: ^1.0.1 version: 1.0.1 + ember-cli-fastboot: + specifier: ^4.1.5 + version: 4.1.5(@babel/core@7.28.5)(ember-source@5.11.1(@glimmer/component@1.1.2(@babel/core@7.28.5))(rsvp@4.8.5)(webpack@5.103.0)) ember-cli-inject-live-reload: specifier: ^2.1.0 version: 2.1.0 @@ -358,6 +361,9 @@ importers: mocha: specifier: ^11.7.5 version: 11.7.5 + prember: + specifier: ^2.0.0 + version: 2.1.0(@babel/core@7.28.5) pretender: specifier: ^3.4.7 version: 3.4.7 @@ -1792,6 +1798,15 @@ packages: '@simple-dom/interface@1.4.0': resolution: {integrity: sha512-l5qumKFWU0S+4ZzMaLXFU8tQZsicHEMEyAxI5kDFGhJsRqDwe0a7/iPA/GdxlGyDKseQQAgIz5kzU7eXTrlSpA==} + '@simple-dom/parser@1.4.0': + resolution: {integrity: sha512-TNjDkOehueRIKr1df416qk9ELj+qWuVVJNIT25y1aZg3pQvxv4UPGrgaDFte7dsWBTbF3V8NYPNQ5FDUZQ8Wlg==} + + '@simple-dom/serializer@1.4.0': + resolution: {integrity: sha512-mI1yRahsVs8atXLiQksineDsFEFqeG7RHwnnBTDOK6inbzl4tZQgjR+Z7edjgIJq5j5RhZvwPI6EuCji9B3eQw==} + + '@simple-dom/void-map@1.4.0': + resolution: {integrity: sha512-VDhLEyVCbuhOBBgHol9ShzIv9O8UCzdXeH4FoXu2DOcu/nnvTjLTck+BgXsCLv5ynDiUdoqsREEVFnoyPpFKVw==} + '@sindresorhus/is@0.14.0': resolution: {integrity: sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==} engines: {node: '>=6'} @@ -1807,6 +1822,10 @@ packages: resolution: {integrity: sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==} engines: {node: '>= 6'} + '@tootallnate/once@2.0.0': + resolution: {integrity: sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==} + engines: {node: '>= 10'} + '@tootallnate/quickjs-emscripten@0.23.0': resolution: {integrity: sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==} @@ -1988,6 +2007,10 @@ packages: '@xtuc/long@4.2.2': resolution: {integrity: sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==} + abab@2.0.6: + resolution: {integrity: sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==} + deprecated: Use your platform's native atob() and btoa() methods instead + abbrev@1.1.1: resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==} @@ -1995,6 +2018,9 @@ packages: resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} engines: {node: '>= 0.6'} + acorn-globals@6.0.0: + resolution: {integrity: sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==} + acorn-import-phases@1.0.4: resolution: {integrity: sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==} engines: {node: '>=10.13.0'} @@ -2182,6 +2208,12 @@ packages: array-flatten@1.1.1: resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==} + array-to-error@1.1.1: + resolution: {integrity: sha512-kqcQ8s7uQfg3UViYON3kCMcck3A9exxgq+riVuKy08Mx00VN4EJhK30L2VpjE58LQHKhcE/GRpvbVUhqTvqzGQ==} + + array-to-sentence@1.1.0: + resolution: {integrity: sha512-YkwkMmPA2+GSGvXj1s9NZ6cc2LBtR+uSeWTy2IGi5MR1Wag4DdrcjTxA/YV/Fw+qKlBeXomneZgThEbm/wvZbw==} + array-union@2.1.0: resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} engines: {node: '>=8'} @@ -2454,6 +2486,9 @@ packages: bluebird@3.7.2: resolution: {integrity: sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==} + blueimp-md5@2.19.0: + resolution: {integrity: sha512-DRQrD6gJyy8FbiE4s+bDoXS9hiW3Vbx5uCdwvcCf3zLHL+Iv7LtGHLpr+GZV8rHG8tK766FGYBwRbu8pELTt+w==} + body-parser@1.20.3: resolution: {integrity: sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} @@ -2513,6 +2548,9 @@ packages: broccoli-caching-writer@3.0.3: resolution: {integrity: sha512-g644Kb5uBPsy+6e2DvO3sOc+/cXZQQNgQt64QQzjA9TSdP0dl5qvetpoNIx4sy/XIjrPYG1smEidq9Z9r61INw==} + broccoli-clean-css@1.1.0: + resolution: {integrity: sha512-S7/RWWX+lL42aGc5+fXVLnwDdMtS0QEWUFalDp03gJ9Na7zj1rWa351N2HZ687E2crM9g+eDWXKzD17cbcTepg==} + broccoli-concat@4.2.5: resolution: {integrity: sha512-dFB5ATPwOyV8S2I7a07HxCoutoq23oY//LhM6Mou86cWUTB174rND5aQLR7Fu8FjFFLxoTbkk7y0VPITJ1IQrw==} engines: {node: 10.* || >= 12.*} @@ -2560,6 +2598,9 @@ packages: broccoli-merge-trees@1.2.4: resolution: {integrity: sha512-RXJAleytlED0dxXGEo2EXwrg5cCesY8LQzzGRogwGQmluoz+ijzxajpyWAW6wu/AyuQZj1vgnIqnld8jvuuXtQ==} + broccoli-merge-trees@2.0.1: + resolution: {integrity: sha512-WjaexJ+I8BxP5V5RNn6um/qDRSmKoiBC/QkRi79FT9ClHfldxRyCDs9mcV7mmoaPlsshmmPaUz5jdtcKA6DClQ==} + broccoli-merge-trees@3.0.2: resolution: {integrity: sha512-ZyPAwrOdlCddduFbsMyyFzJUrvW6b04pMvDiAQZrCwghlvgowJDY+EfoXn+eR1RRA5nmGHJ+B68T63VnpRiT1A==} engines: {node: '>=6.0.0'} @@ -2634,6 +2675,10 @@ packages: broccoli-sri-hash@2.1.2: resolution: {integrity: sha512-toLD/v7ut2ajcH8JsdCMG2Bpq2qkwTcKM6CMzVMSAJjaz/KpK69fR+gSqe1dsjh+QTdxG0yVvkq3Sij/XMzV6A==} + broccoli-stew@1.6.0: + resolution: {integrity: sha512-sUwCJNnYH4Na690By5xcEMAZqKgquUQnMAEuIiL3Z2k63mSw9Xg+7Ew4wCrFrMmXMcLpWjZDOm6Yqnq268N+ZQ==} + engines: {node: ^4.5 || 6.* || >= 7.*} + broccoli-stew@3.0.0: resolution: {integrity: sha512-NXfi+Vas24n3Ivo21GvENTI55qxKu7OwKRnCLWXld8MiLiQKQlWIq28eoARaFj0lTUFwUa4jKZeA7fW9PiWQeg==} engines: {node: 8.* || >= 10.*} @@ -2650,6 +2695,9 @@ packages: resolution: {integrity: sha512-sWi3b3fTUSVPDsz5KsQ5eCQNVAtLgkIE/HYFkEZXR/07clqmd4E/gFiuwSaqa9b+QTXc1Uemfb7TVWbEIURWDg==} engines: {node: 8.* || >= 10.*} + browser-process-hrtime@1.0.0: + resolution: {integrity: sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==} + browser-stdout@1.3.1: resolution: {integrity: sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==} @@ -2834,6 +2882,14 @@ packages: clean-base-url@1.0.0: resolution: {integrity: sha512-9q6ZvUAhbKOSRFY7A/irCQ/rF0KIpa3uXpx6izm8+fp7b2H4hLeUJ+F1YYk9+gDQ/X8Q0MEyYs+tG3cht//HTg==} + clean-css-promise@0.1.1: + resolution: {integrity: sha512-tzWkANXMD70ETa/wAu2TXAAxYWS0ZjVUFM2dVik8RQBoAbGMFJv4iVluz3RpcoEbo++fX4RV/BXfgGoOjp8o3Q==} + + clean-css@3.4.28: + resolution: {integrity: sha512-aTWyttSdI2mYi07kWqHi24NUU9YlELFKGOAgFzZjDN1064DMAOy2FBuoyGmkKRlXkbpXd0EVHmiVkbKhKoirTw==} + engines: {node: '>=0.10.0'} + hasBin: true + clean-css@5.3.3: resolution: {integrity: sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg==} engines: {node: '>= 10.0'} @@ -2968,6 +3024,10 @@ packages: commander@2.20.3: resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} + commander@2.8.1: + resolution: {integrity: sha512-+pJLBFVk+9ZZdlAOB5WuIElVPPth47hILFkmGym57aq8kwxsowvByvB0DHs1vQAhyMZzdcpTtF0VDKGkSDR4ZQ==} + engines: {node: '>= 0.6.x'} + commander@4.1.1: resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} engines: {node: '>= 6'} @@ -3224,6 +3284,10 @@ packages: cookie-signature@1.0.6: resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==} + cookie@0.4.2: + resolution: {integrity: sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==} + engines: {node: '>= 0.6'} + cookie@0.7.1: resolution: {integrity: sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==} engines: {node: '>= 0.6'} @@ -3347,6 +3411,16 @@ packages: resolution: {integrity: sha512-vrqULLffYU1Q2tLdJvaCYbONStnfkfimRxXNaGjxMldI0C7JPBC4rB1RyjhfdZ4m1frm8pM9uRPKH3d2knZ8gg==} engines: {node: '>=0.10.0'} + cssom@0.3.8: + resolution: {integrity: sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==} + + cssom@0.5.0: + resolution: {integrity: sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==} + + cssstyle@2.3.0: + resolution: {integrity: sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==} + engines: {node: '>=8'} + cssstyle@4.6.0: resolution: {integrity: sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==} engines: {node: '>=18'} @@ -3362,6 +3436,10 @@ packages: resolution: {integrity: sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==} engines: {node: '>= 14'} + data-urls@3.0.2: + resolution: {integrity: sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==} + engines: {node: '>=12'} + data-urls@5.0.0: resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==} engines: {node: '>=18'} @@ -3607,6 +3685,11 @@ packages: domelementtype@2.3.0: resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} + domexception@4.0.0: + resolution: {integrity: sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==} + engines: {node: '>=12'} + deprecated: Use your platform's native DOMException instead + domhandler@5.0.3: resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} engines: {node: '>= 4'} @@ -3733,6 +3816,12 @@ packages: resolution: {integrity: sha512-x6vtuxruExD66qo/OvNldGbxZ3NTaeRSdL63Q9clJ/+JspLXeHtKxl5dDMqRNvl8sItVrZJqq9GYMJfPsg5SWA==} engines: {node: 12.* || 14.* || >= 16} + ember-cli-fastboot@4.1.5: + resolution: {integrity: sha512-XVigHzn+xXMqvovdrPNQHXRCzVOkU78ij6adU8Qt7PAaF3stR9oPh/35f30aJ2vcL6jwR72glnuCyXpm3EL22A==} + engines: {node: 14.* || 16.* || >= 18} + peerDependencies: + ember-source: ~5.11.1 + ember-cli-get-component-path-option@1.0.0: resolution: {integrity: sha512-k47TDwcJ2zPideBCZE8sCiShSxQSpebY2BHcX2DdipMmBox5gsfyVrbKJWIHeSTTKyEUgmBIvQkqTOozEziCZA==} @@ -3774,6 +3863,9 @@ packages: resolution: {integrity: sha512-S2HQqmNtcezmLSt/OPZKCXg+aRV7yFoZp+tn1HCLSbR/eU95xl7MWxTjbj/wOIGMfhggy/hBT2+STDh8mGuVpw==} engines: {node: '>= 14'} + ember-cli-preprocess-registry@3.3.0: + resolution: {integrity: sha512-60GYpw7VPeB7TvzTLZTuLTlHdOXvayxjAQ+IxM2T04Xkfyu75O2ItbWlftQW7NZVGkaCsXSRAmn22PG03VpLMA==} + ember-cli-preprocess-registry@5.0.1: resolution: {integrity: sha512-Jb2zbE5Kfe56Nf4IpdaQ10zZ72p/RyLdgE5j5/lKG3I94QHlq+7AkAd18nPpb5OUeRUT13yQTAYpU+MbjpKTtg==} engines: {node: 16.* || >= 18} @@ -4333,6 +4425,21 @@ packages: fast-uri@3.1.0: resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} + fastboot-express-middleware@4.1.2: + resolution: {integrity: sha512-vnzEBV7gZ3lSoGiqG/7+006nHNA3z+ZnU/5u9jPHtKpjH28yEbvZq6PnAeTu24UR98jZVR0pnFbfX0co+O9PeA==} + engines: {node: 12.* || 14.* || >=16} + + fastboot-transform@0.1.3: + resolution: {integrity: sha512-6otygPIJw1ARp1jJb+6KVO56iKBjhO+5x59RSC9qiZTbZRrv+HZAuP00KD3s+nWMvcFDemtdkugki9DNFTTwCQ==} + + fastboot@4.1.2: + resolution: {integrity: sha512-VJLmF0xdCNwIIuA7DQtN1KTAKfEGsbZGJ0cfKh64h6DeMh3Fhr2FCCxkPh8zYqGoqzjXFdFbtk60WS3f6HKqBg==} + engines: {node: 12.* || 14.* || >=16} + + fastboot@4.1.5: + resolution: {integrity: sha512-2FkJWrpxgJjy5kLb3KrYp0pKdB4WgT/6qxtQO7ozYtQqMBOAARMnp59xp/Hdosa1cE2jslZgwDAv3v11OlQfAw==} + engines: {node: 12.* || 14.* || >=16} + fastest-levenshtein@1.0.16: resolution: {integrity: sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==} engines: {node: '>= 4.9.1'} @@ -4599,6 +4706,10 @@ packages: resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} engines: {node: '>= 0.4'} + get-stdin@4.0.1: + resolution: {integrity: sha512-F5aQMywwJ2n85s4hJPTT9RPxGmubonuB10MNYo17/xph174n2MIR33HRguhzVag10O/npM7SPk73LMZNP+FaWw==} + engines: {node: '>=0.10.0'} + get-stdin@9.0.0: resolution: {integrity: sha512-dVKBjfWisLAicarI2Sf+JuBE/DghV4UzNAVe9yhEJuzeREd3JhOTE9cUaJTeSa77fsbQUK3pcOpJfM59+VKZaA==} engines: {node: '>=12'} @@ -4758,6 +4869,9 @@ packages: graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + graceful-readlink@1.0.1: + resolution: {integrity: sha512-8tLu60LgxF6XpdbK8OW3FA+IfTNBn1ZHGHKF4KQbEeSkajYw5PlYJcKluntgegDPTg8UkHjpet1T82vk6TQ68w==} + graphemer@1.4.0: resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} @@ -4888,6 +5002,10 @@ packages: resolution: {integrity: sha512-Rw/B2DNQaPBICNXEm8balFz9a6WpZrkCGpcWFpy7nCj+NyhSdqXipmfvtmWt9xGfp0wZnBxB+iVpLmQMYt47Tw==} engines: {node: ^18.17.0 || >=20.5.0} + html-encoding-sniffer@3.0.0: + resolution: {integrity: sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==} + engines: {node: '>=12'} + html-encoding-sniffer@4.0.0: resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==} engines: {node: '>=18'} @@ -4920,6 +5038,10 @@ packages: resolution: {integrity: sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==} engines: {node: '>= 6'} + http-proxy-agent@5.0.0: + resolution: {integrity: sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==} + engines: {node: '>= 6'} + http-proxy-agent@7.0.2: resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} engines: {node: '>= 14'} @@ -5029,6 +5151,10 @@ packages: ini@1.3.8: resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} + inline-source-map-comment@1.0.5: + resolution: {integrity: sha512-a3/m6XgooVCXkZCduOb7pkuvUtNKt4DaqaggKKJrMQHQsqt6JcJXEreExeZiiK4vWL/cM/uF6+chH05pz2/TdQ==} + hasBin: true + inquirer@12.9.6: resolution: {integrity: sha512-603xXOgyfxhuis4nfnWaZrMaotNT0Km9XwwBNWUKbIDqeCY89jGr2F9YPEMiNhU6XjIP4VoWISMBFfcc5NgrTw==} engines: {node: '>=18'} @@ -5399,6 +5525,15 @@ packages: resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} hasBin: true + jsdom@19.0.0: + resolution: {integrity: sha512-RYAyjCbxy/vri/CfnjUWJQQtZ3LKlLnDqj+9XLNnJPgEGeirZs3hllKR20re8LUZ6o1b1X4Jat+Qd26zmP41+A==} + engines: {node: '>=12'} + peerDependencies: + canvas: ^2.5.0 + peerDependenciesMeta: + canvas: + optional: true + jsdom@23.2.0: resolution: {integrity: sha512-L88oL7D/8ufIES+Zjz7v0aes+oBMh2Xnh3ygWvL0OaICOomKEPKuPnIfBJekiXr+BHbbMjrWn/xqrDQuxFTeyA==} engines: {node: '>=18'} @@ -5753,6 +5888,10 @@ packages: mathml-tag-names@2.1.3: resolution: {integrity: sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg==} + md5-hex@3.0.1: + resolution: {integrity: sha512-BUiRtTtV39LIJwinWBjqVsU9xhdnz7/i889V859IBFpuqGAj6LuOvHv5XLbgZ2R7ptJoJaEcxkv88/h25T7Ciw==} + engines: {node: '>=8'} + mdast-util-from-markdown@2.0.2: resolution: {integrity: sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==} @@ -5799,6 +5938,9 @@ packages: merge-stream@2.0.0: resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + merge-trees@1.0.1: + resolution: {integrity: sha512-O7TWwipLHhc9tErjq3WBvNP7I1g7Wgudl1ZkLqpT7F2MZy1yEdgnI9cpZZxBaqk+wJZu+2b9FE7D3ubUmGFHFA==} + merge-trees@2.0.0: resolution: {integrity: sha512-5xBbmqYBalWqmhYm51XlohhkmVOua3VAUrrWh8t9iOkaLpS6ifqm/UVuUjQCeDVJ9Vx3g2l6ihfkbLSTeKsHbw==} @@ -6130,6 +6272,9 @@ packages: node-int64@0.4.0: resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} + node-modules-path@1.0.2: + resolution: {integrity: sha512-6Gbjq+d7uhkO7epaKi5DNgUJn7H0gEyA4Jg0Mo1uQOi3Rk50G83LtmhhFyw0LxnAFhtlspkiiw52ISP13qzcBg==} + node-notifier@10.0.1: resolution: {integrity: sha512-YX7TSyDukOZ0g+gmzjB6abKu+hTGvO8+8+gIFDsRCU2t8fLV/P2unmt+LGFaIa4y64aX98Qksa97rgz4vMNeLQ==} @@ -6206,6 +6351,9 @@ packages: num2fraction@1.2.2: resolution: {integrity: sha512-Y1wZESM7VUThYY+4W+X4ySH2maqcA+p7UR+w8VWNWVAd6lwuXXWz/w/Cz43J/dI2I+PS6wD5N+bJUF+gjWvIqg==} + nwsapi@2.2.23: + resolution: {integrity: sha512-7wfH4sLbt4M0gCDzGE6vzQBo0bfTKjU7Sfpqy/7gs1qBfYz2vEJH6vXcBKpO3+6Yu1telwd0t9HpyOoLEQQbIQ==} + nypm@0.6.2: resolution: {integrity: sha512-7eM+hpOtrKrBDCh7Ypu2lJ9Z7PNZBdi/8AT3AX8xoCj43BBVHD0hPSTEvMtkMpfs8FCqBGhxB+uToIQimA111g==} engines: {node: ^14.16.0 || >=16.10.0} @@ -6554,6 +6702,14 @@ packages: resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} engines: {node: '>=0.10.0'} + pinkie-promise@2.0.1: + resolution: {integrity: sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==} + engines: {node: '>=0.10.0'} + + pinkie@2.0.4: + resolution: {integrity: sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==} + engines: {node: '>=0.10.0'} + pkg-dir@4.2.0: resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} engines: {node: '>=8'} @@ -6674,6 +6830,10 @@ packages: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} + prember@2.1.0: + resolution: {integrity: sha512-bK3lwDIm9lP1YemNy1cLImpxT0a4XYWJ8WHztUtGtLrXPJuRGtNUXbHx9d73Pf3RfhqY4fzSQCbR2HEtBcG8jQ==} + engines: {node: 12.* || 14.* || >= 16} + prepend-http@2.0.0: resolution: {integrity: sha512-ravE6m9Atw9Z/jjttRUZ+clIXogdghyZAuWJ3qEzjT+jI/dL1ifAqhZeC5VHzQp1MSt1+jxKkFNemj/iO7tVUA==} engines: {node: '>=4'} @@ -6711,6 +6871,9 @@ packages: resolution: {integrity: sha512-++Vn7NS4Xf9NacaU9Xq3URUuqZETPsf8L4j5/ckhaRYsfPeRyzGw+iDjFhV/Jr3uNmTvvddEJFWh5R1gRgUH8A==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + process-relative-require@1.0.0: + resolution: {integrity: sha512-r8G5WJPozMJAiv8sDdVWKgJ4In/zBXqwJdMCGAXQt2Kd3HdbAuJVzWYM4JW150hWoaI9DjhtbjcsCCHIMxm8RA==} + progress@2.0.3: resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==} engines: {node: '>=0.4.0'} @@ -6886,6 +7049,10 @@ packages: resolution: {integrity: sha512-XNvYvkfdAN9QewbrxeTOjgINkdY/odTgTS56ZNEWL9Ml0weT4T3sFtvnTuF+Gxyu46ANcRm1ntrF6F5LAJPAaQ==} engines: {node: '>= 4'} + recast@0.19.1: + resolution: {integrity: sha512-8FCjrBxjeEU2O6I+2hyHyBFH1siJbMBLwIRvVr1T3FD2cL754sOaJDsJ/8h3xYltasbJ8jqWRIhMuDGBSiSbjw==} + engines: {node: '>= 4'} + redent@4.0.0: resolution: {integrity: sha512-tYkDkVVtYkSVhuQ4zBgfvciymHaeuel+zFKXShfDnFP5SyVEP7qo70Rf1jTOTCx3vGNAbnEi/xFkcfQVMIBWag==} engines: {node: '>=12'} @@ -7190,6 +7357,10 @@ packages: sax@1.2.4: resolution: {integrity: sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==} + saxes@5.0.1: + resolution: {integrity: sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==} + engines: {node: '>=10'} + saxes@6.0.0: resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} engines: {node: '>=v12.22.7'} @@ -7315,6 +7486,9 @@ packages: silent-error@1.1.1: resolution: {integrity: sha512-n4iEKyNcg4v6/jpb3c0/iyH2G1nzUNl7Gpqtn/mHIJK9S/q/7MCfoO4rwVOoO59qPFIc0hVHvMbiOJ0NdtxKKw==} + simple-dom@1.4.0: + resolution: {integrity: sha512-TnBPkmOyjdaOqyBMb4ick+n8c0Xv9Iwg1PykFV7hz9Se3UCiacTbRb+25cPmvozFNJLBUNvUzX/KsPfXF14ivA==} + simple-html-tokenizer@0.5.11: resolution: {integrity: sha512-C2WEK/Z3HoSFbYq8tI7ni3eOo/NneSPRoPpcM7WdLjFOArFuyXEjAoCdOC3DgMfRyziZQ1hCNR4mrNdWEvD0og==} @@ -7628,6 +7802,9 @@ packages: engines: {node: ^14.13.1 || >=16.0.0} hasBin: true + sum-up@1.0.3: + resolution: {integrity: sha512-zw5P8gnhiqokJUWRdR6F4kIIIke0+ubQSGyYUY506GCbJWtV7F6Xuy0j6S125eSX2oF+a8KdivsZ8PlVEH0Mcw==} + supports-color@2.0.0: resolution: {integrity: sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==} engines: {node: '>=0.8.0'} @@ -7849,6 +8026,10 @@ packages: tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + tr46@3.0.0: + resolution: {integrity: sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==} + engines: {node: '>=12'} + tr46@5.1.1: resolution: {integrity: sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==} engines: {node: '>=18'} @@ -8125,6 +8306,14 @@ packages: velocity-animate@1.5.2: resolution: {integrity: sha512-m6EXlCAMetKztO1ppBhGU1/1MR3IiEevO6ESq6rcrSQ3Q77xYSW13jkfXW88o4xMrkXJhy/U7j4wFR/twMB0Eg==} + w3c-hr-time@1.0.2: + resolution: {integrity: sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==} + deprecated: Use your platform's native performance.now() and performance.timeOrigin. + + w3c-xmlserializer@3.0.0: + resolution: {integrity: sha512-3WFqGEgSXIyGhOmAFtlicJNMjEps8b1MG31NCA0/vOF9+nKMUW1ckhi9cnNHmf88Rzw5V+dwIwsm2C7X8k9aQg==} + engines: {node: '>=12'} + w3c-xmlserializer@5.0.0: resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} engines: {node: '>=18'} @@ -8189,14 +8378,31 @@ packages: resolution: {integrity: sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==} engines: {node: '>=0.8.0'} + whatwg-encoding@2.0.0: + resolution: {integrity: sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==} + engines: {node: '>=12'} + deprecated: Use @exodus/bytes instead for a more spec-conformant and faster implementation + whatwg-encoding@3.1.1: resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==} engines: {node: '>=18'} + whatwg-mimetype@3.0.0: + resolution: {integrity: sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==} + engines: {node: '>=12'} + whatwg-mimetype@4.0.0: resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==} engines: {node: '>=18'} + whatwg-url@10.0.0: + resolution: {integrity: sha512-CLxxCmdUby142H5FZzn4D8ikO1cmypvXVQktsgosNy4a4BHrDHeciBBGZhb0bNoR5/MltoCatso+vFjjGx8t0w==} + engines: {node: '>=12'} + + whatwg-url@11.0.0: + resolution: {integrity: sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==} + engines: {node: '>=12'} + whatwg-url@14.2.0: resolution: {integrity: sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==} engines: {node: '>=18'} @@ -8311,6 +8517,10 @@ packages: resolution: {integrity: sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==} engines: {node: '>=8'} + xml-name-validator@4.0.0: + resolution: {integrity: sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==} + engines: {node: '>=12'} + xml-name-validator@5.0.0: resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} engines: {node: '>=18'} @@ -10079,6 +10289,16 @@ snapshots: '@simple-dom/interface@1.4.0': {} + '@simple-dom/parser@1.4.0': + dependencies: + '@simple-dom/interface': 1.4.0 + + '@simple-dom/serializer@1.4.0': + dependencies: + '@simple-dom/interface': 1.4.0 + + '@simple-dom/void-map@1.4.0': {} + '@sindresorhus/is@0.14.0': {} '@socket.io/component-emitter@3.1.2': {} @@ -10089,6 +10309,8 @@ snapshots: '@tootallnate/once@1.1.2': {} + '@tootallnate/once@2.0.0': {} + '@tootallnate/quickjs-emscripten@0.23.0': {} '@types/body-parser@1.19.6': @@ -10180,7 +10402,7 @@ snapshots: '@types/minimatch@6.0.0': dependencies: - minimatch: 7.4.6 + minimatch: 9.0.5 '@types/minimist@1.2.5': {} @@ -10316,6 +10538,8 @@ snapshots: '@xtuc/long@4.2.2': {} + abab@2.0.6: {} + abbrev@1.1.1: {} accepts@1.3.8: @@ -10323,6 +10547,11 @@ snapshots: mime-types: 2.1.35 negotiator: 0.6.3 + acorn-globals@6.0.0: + dependencies: + acorn: 7.4.1 + acorn-walk: 7.2.0 + acorn-import-phases@1.0.4(acorn@8.15.0): dependencies: acorn: 8.15.0 @@ -10482,6 +10711,12 @@ snapshots: array-flatten@1.1.1: {} + array-to-error@1.1.1: + dependencies: + array-to-sentence: 1.1.0 + + array-to-sentence@1.1.0: {} + array-union@2.1.0: {} array-unique@0.3.2: {} @@ -10859,6 +11094,8 @@ snapshots: bluebird@3.7.2: {} + blueimp-md5@2.19.0: {} + body-parser@1.20.3: dependencies: bytes: 3.1.2 @@ -10997,6 +11234,15 @@ snapshots: transitivePeerDependencies: - supports-color + broccoli-clean-css@1.1.0: + dependencies: + broccoli-persistent-filter: 1.4.6 + clean-css-promise: 0.1.1 + inline-source-map-comment: 1.0.5 + json-stable-stringify: 1.3.0 + transitivePeerDependencies: + - supports-color + broccoli-concat@4.2.5: dependencies: broccoli-debug: 0.6.5 @@ -11136,6 +11382,13 @@ snapshots: transitivePeerDependencies: - supports-color + broccoli-merge-trees@2.0.1: + dependencies: + broccoli-plugin: 1.3.1 + merge-trees: 1.0.1 + transitivePeerDependencies: + - supports-color + broccoli-merge-trees@3.0.2: dependencies: broccoli-plugin: 1.3.1 @@ -11304,6 +11557,25 @@ snapshots: transitivePeerDependencies: - supports-color + broccoli-stew@1.6.0: + dependencies: + broccoli-debug: 0.6.5 + broccoli-funnel: 2.0.2 + broccoli-merge-trees: 2.0.1 + broccoli-persistent-filter: 1.4.6 + broccoli-plugin: 1.3.1 + chalk: 2.4.2 + debug: 3.2.7 + ensure-posix-path: 1.1.1 + fs-extra: 5.0.0 + minimatch: 3.1.2 + resolve: 1.22.11 + rsvp: 4.8.5 + symlink-or-copy: 1.3.1 + walk-sync: 0.3.4 + transitivePeerDependencies: + - supports-color + broccoli-stew@3.0.0: dependencies: broccoli-debug: 0.6.5 @@ -11375,6 +11647,8 @@ snapshots: transitivePeerDependencies: - supports-color + browser-process-hrtime@1.0.0: {} + browser-stdout@1.3.1: {} browserslist@4.28.0: @@ -11626,6 +11900,17 @@ snapshots: clean-base-url@1.0.0: {} + clean-css-promise@0.1.1: + dependencies: + array-to-error: 1.1.1 + clean-css: 3.4.28 + pinkie-promise: 2.0.1 + + clean-css@3.4.28: + dependencies: + commander: 2.8.1 + source-map: 0.4.4 + clean-css@5.3.3: dependencies: source-map: 0.6.1 @@ -11756,6 +12041,10 @@ snapshots: commander@2.20.3: {} + commander@2.8.1: + dependencies: + graceful-readlink: 1.0.1 + commander@4.1.1: {} commander@5.1.0: {} @@ -11851,13 +12140,14 @@ snapshots: continuable-cache@0.3.1: {} - convert-source-map@1.9.0: - optional: true + convert-source-map@1.9.0: {} convert-source-map@2.0.0: {} cookie-signature@1.0.6: {} + cookie@0.4.2: {} + cookie@0.7.1: {} cookie@0.7.2: {} @@ -11993,6 +12283,14 @@ snapshots: dependencies: css-tree: 1.0.0-alpha.29 + cssom@0.3.8: {} + + cssom@0.5.0: {} + + cssstyle@2.3.0: + dependencies: + cssom: 0.3.8 + cssstyle@4.6.0: dependencies: '@asamuzakjp/css-color': 3.2.0 @@ -12005,6 +12303,12 @@ snapshots: data-uri-to-buffer@6.0.2: {} + data-urls@3.0.2: + dependencies: + abab: 2.0.6 + whatwg-mimetype: 3.0.0 + whatwg-url: 11.0.0 + data-urls@5.0.0: dependencies: whatwg-mimetype: 4.0.0 @@ -12221,6 +12525,10 @@ snapshots: domelementtype@2.3.0: {} + domexception@4.0.0: + dependencies: + webidl-conversions: 7.0.0 + domhandler@5.0.3: dependencies: domelementtype: 2.3.0 @@ -12502,6 +12810,34 @@ snapshots: transitivePeerDependencies: - supports-color + ember-cli-fastboot@4.1.5(@babel/core@7.28.5)(ember-source@5.11.1(@glimmer/component@1.1.2(@babel/core@7.28.5))(rsvp@4.8.5)(webpack@5.103.0)): + dependencies: + broccoli-concat: 4.2.5 + broccoli-file-creator: 2.1.1 + broccoli-funnel: 3.0.8 + broccoli-merge-trees: 4.2.0 + broccoli-plugin: 4.0.7 + chalk: 4.1.2 + ember-cli-babel: 8.2.0(@babel/core@7.28.5) + ember-cli-lodash-subset: 2.0.1 + ember-cli-preprocess-registry: 3.3.0 + ember-cli-version-checker: 5.1.2 + ember-source: 5.11.1(@glimmer/component@1.1.2(@babel/core@7.28.5))(rsvp@4.8.5)(webpack@5.103.0) + fastboot: 4.1.5 + fastboot-express-middleware: 4.1.2 + fastboot-transform: 0.1.3 + fs-extra: 10.1.0 + json-stable-stringify: 1.3.0 + md5-hex: 3.0.1 + recast: 0.19.1 + silent-error: 1.1.1 + transitivePeerDependencies: + - '@babel/core' + - bufferutil + - canvas + - supports-color + - utf-8-validate + ember-cli-get-component-path-option@1.0.0: {} ember-cli-htmlbars@6.3.0: @@ -12578,6 +12914,15 @@ snapshots: - '@babel/core' - supports-color + ember-cli-preprocess-registry@3.3.0: + dependencies: + broccoli-clean-css: 1.1.0 + broccoli-funnel: 2.0.2 + debug: 3.2.7 + process-relative-require: 1.0.0 + transitivePeerDependencies: + - supports-color + ember-cli-preprocess-registry@5.0.1: dependencies: broccoli-funnel: 3.0.8 @@ -13729,6 +14074,53 @@ snapshots: fast-uri@3.1.0: {} + fastboot-express-middleware@4.1.2: + dependencies: + chalk: 4.1.2 + fastboot: 4.1.2 + transitivePeerDependencies: + - bufferutil + - canvas + - supports-color + - utf-8-validate + + fastboot-transform@0.1.3: + dependencies: + broccoli-stew: 1.6.0 + convert-source-map: 1.9.0 + transitivePeerDependencies: + - supports-color + + fastboot@4.1.2: + dependencies: + chalk: 4.1.2 + cookie: 0.4.2 + debug: 4.4.3(supports-color@8.1.1) + jsdom: 19.0.0 + resolve: 1.22.11 + simple-dom: 1.4.0 + source-map-support: 0.5.21 + transitivePeerDependencies: + - bufferutil + - canvas + - supports-color + - utf-8-validate + + fastboot@4.1.5: + dependencies: + chalk: 4.1.2 + cookie: 0.4.2 + debug: 4.4.3(supports-color@8.1.1) + jsdom: 19.0.0 + resolve: 1.22.11 + simple-dom: 1.4.0 + source-map-support: 0.5.21 + transitivePeerDependencies: + - bufferutil + - canvas + - supports-color + - utf-8-validate + fastest-levenshtein@1.0.16: {} fastq@1.19.1: @@ -14086,6 +14478,8 @@ snapshots: dunder-proto: 1.0.1 es-object-atoms: 1.1.1 + get-stdin@4.0.1: {} + get-stdin@9.0.0: {} get-stream@3.0.0: {} @@ -14293,6 +14687,8 @@ snapshots: graceful-fs@4.2.11: {} + graceful-readlink@1.0.1: {} + graphemer@1.4.0: {} growly@1.3.0: {} @@ -14434,6 +14830,10 @@ snapshots: dependencies: lru-cache: 10.4.3 + html-encoding-sniffer@3.0.0: + dependencies: + whatwg-encoding: 2.0.0 + html-encoding-sniffer@4.0.0: dependencies: whatwg-encoding: 3.1.1 @@ -14476,6 +14876,14 @@ snapshots: transitivePeerDependencies: - supports-color + http-proxy-agent@5.0.0: + dependencies: + '@tootallnate/once': 2.0.0 + agent-base: 6.0.2 + debug: 4.4.3(supports-color@8.1.1) + transitivePeerDependencies: + - supports-color + http-proxy-agent@7.0.2: dependencies: agent-base: 7.1.4 @@ -14576,6 +14984,14 @@ snapshots: ini@1.3.8: {} + inline-source-map-comment@1.0.5: + dependencies: + chalk: 1.1.3 + get-stdin: 4.0.1 + minimist: 1.2.8 + sum-up: 1.0.3 + xtend: 4.0.2 + inquirer@12.9.6(@types/node@24.10.1): dependencies: '@inquirer/ansi': 1.0.2 @@ -14945,6 +15361,40 @@ snapshots: dependencies: argparse: 2.0.1 + jsdom@19.0.0: + dependencies: + abab: 2.0.6 + acorn: 8.15.0 + acorn-globals: 6.0.0 + cssom: 0.5.0 + cssstyle: 2.3.0 + data-urls: 3.0.2 + decimal.js: 10.6.0 + domexception: 4.0.0 + escodegen: 2.1.0 + form-data: 4.0.5 + html-encoding-sniffer: 3.0.0 + http-proxy-agent: 5.0.0 + https-proxy-agent: 5.0.1 + is-potential-custom-element-name: 1.0.1 + nwsapi: 2.2.23 + parse5: 6.0.1 + saxes: 5.0.1 + symbol-tree: 3.2.4 + tough-cookie: 4.1.4 + w3c-hr-time: 1.0.2 + w3c-xmlserializer: 3.0.0 + webidl-conversions: 7.0.0 + whatwg-encoding: 2.0.0 + whatwg-mimetype: 3.0.0 + whatwg-url: 10.0.0 + ws: 8.18.3 + xml-name-validator: 4.0.0 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + jsdom@23.2.0: dependencies: '@asamuzakjp/dom-selector': 2.0.2 @@ -15331,6 +15781,10 @@ snapshots: mathml-tag-names@2.1.3: {} + md5-hex@3.0.1: + dependencies: + blueimp-md5: 2.19.0 + mdast-util-from-markdown@2.0.2: dependencies: '@types/mdast': 4.0.4 @@ -15398,6 +15852,17 @@ snapshots: merge-stream@2.0.0: {} + merge-trees@1.0.1: + dependencies: + can-symlink: 1.0.0 + fs-tree-diff: 0.5.9 + heimdalljs: 0.2.6 + heimdalljs-logger: 0.1.10 + rimraf: 2.7.1 + symlink-or-copy: 1.3.1 + transitivePeerDependencies: + - supports-color + merge-trees@2.0.0: dependencies: fs-updater: 1.0.4 @@ -15806,6 +16271,8 @@ snapshots: node-int64@0.4.0: {} + node-modules-path@1.0.2: {} + node-notifier@10.0.1: dependencies: growly: 1.3.0 @@ -15884,6 +16351,8 @@ snapshots: num2fraction@1.2.2: {} + nwsapi@2.2.23: {} + nypm@0.6.2: dependencies: citty: 0.1.6 @@ -16238,6 +16707,12 @@ snapshots: pify@2.3.0: {} + pinkie-promise@2.0.1: + dependencies: + pinkie: 2.0.4 + + pinkie@2.0.4: {} + pkg-dir@4.2.0: dependencies: find-up: 4.1.0 @@ -16364,6 +16839,23 @@ snapshots: prelude-ls@1.2.1: {} + prember@2.1.0(@babel/core@7.28.5): + dependencies: + broccoli-debug: 0.6.5 + broccoli-merge-trees: 4.2.0 + broccoli-plugin: 4.0.7 + chalk: 4.1.2 + ember-cli-babel: 8.2.0(@babel/core@7.28.5) + express: 4.21.2 + fastboot: 4.1.5 + mkdirp: 3.0.1 + transitivePeerDependencies: + - '@babel/core' + - bufferutil + - canvas + - supports-color + - utf-8-validate + prepend-http@2.0.0: {} pretender@3.4.7: @@ -16387,6 +16879,10 @@ snapshots: proc-log@3.0.0: {} + process-relative-require@1.0.0: + dependencies: + node-modules-path: 1.0.2 + progress@2.0.3: {} promise-inflight@1.0.1: {} @@ -16587,6 +17083,13 @@ snapshots: private: 0.1.8 source-map: 0.6.1 + recast@0.19.1: + dependencies: + ast-types: 0.13.3 + esprima: 4.0.1 + private: 0.1.8 + source-map: 0.6.1 + redent@4.0.0: dependencies: indent-string: 5.0.0 @@ -16931,6 +17434,10 @@ snapshots: sax@1.2.4: {} + saxes@5.0.1: + dependencies: + xmlchars: 2.2.0 + saxes@6.0.0: dependencies: xmlchars: 2.2.0 @@ -17086,6 +17593,14 @@ snapshots: transitivePeerDependencies: - supports-color + simple-dom@1.4.0: + dependencies: + '@simple-dom/document': 1.4.0 + '@simple-dom/interface': 1.4.0 + '@simple-dom/parser': 1.4.0 + '@simple-dom/serializer': 1.4.0 + '@simple-dom/void-map': 1.4.0 + simple-html-tokenizer@0.5.11: {} simple-swizzle@0.2.4: @@ -17476,6 +17991,10 @@ snapshots: - supports-color - typescript + sum-up@1.0.3: + dependencies: + chalk: 1.1.3 + supports-color@2.0.0: {} supports-color@5.5.0: @@ -17827,6 +18346,10 @@ snapshots: tr46@0.0.3: {} + tr46@3.0.0: + dependencies: + punycode: 2.3.1 + tr46@5.1.1: dependencies: punycode: 2.3.1 @@ -18089,6 +18612,14 @@ snapshots: velocity-animate@1.5.2: {} + w3c-hr-time@1.0.2: + dependencies: + browser-process-hrtime: 1.0.0 + + w3c-xmlserializer@3.0.0: + dependencies: + xml-name-validator: 4.0.0 + w3c-xmlserializer@5.0.0: dependencies: xml-name-validator: 5.0.0 @@ -18190,12 +18721,28 @@ snapshots: websocket-extensions@0.1.4: {} + whatwg-encoding@2.0.0: + dependencies: + iconv-lite: 0.6.3 + whatwg-encoding@3.1.1: dependencies: iconv-lite: 0.6.3 + whatwg-mimetype@3.0.0: {} + whatwg-mimetype@4.0.0: {} + whatwg-url@10.0.0: + dependencies: + tr46: 3.0.0 + webidl-conversions: 7.0.0 + + whatwg-url@11.0.0: + dependencies: + tr46: 3.0.0 + webidl-conversions: 7.0.0 + whatwg-url@14.2.0: dependencies: tr46: 5.1.1 @@ -18319,6 +18866,8 @@ snapshots: xdg-basedir@4.0.0: {} + xml-name-validator@4.0.0: {} + xml-name-validator@5.0.0: {} xmlchars@2.2.0: {} diff --git a/tests-node/unit/deploy/plugin-test.js b/tests-node/unit/deploy/plugin-test.js index b0a6ee35c..044747939 100644 --- a/tests-node/unit/deploy/plugin-test.js +++ b/tests-node/unit/deploy/plugin-test.js @@ -2,22 +2,22 @@ const assert = require('chai').assert; const { - replaceAddonDocsRootURL, + replaceDeployTokens, } = require('../../../lib/utils/find-and-replace-in-directory'); describe('`deploy` | plugin test', function () { - it('replaceAddonDocsRootURL in index.html', function () { + it('replaceDeployTokens in index.html (new format with rootURL=/)', function () { const contents = `
- + - - - - + + + + `; @@ -31,7 +31,7 @@ describe('`deploy` | plugin test', function () { }), ); const addonDocsRootURL = '/my-addon/versions/main/'; - const actual = replaceAddonDocsRootURL( + const actual = replaceDeployTokens( contents, addonDocsRootURL, encodedVersion, @@ -53,9 +53,9 @@ describe('`deploy` | plugin test', function () { assert.equal(actual, expected); }); - it('replaceAddonDocsRootURL in chunks', function () { - const chunk = - '(e.children=[]),e),o.p="ADDON_DOCS_ROOT_URL/assets/",(()=>{var e={143:0}'; + + it('replaceDeployTokens in chunks (new format with /assets/)', function () { + const chunk = '(e.children=[]),e),o.p="/assets/",(()=>{var e={143:0}'; const encodedVersion = encodeURIComponent( JSON.stringify({ path: 'versions/main', @@ -66,13 +66,74 @@ describe('`deploy` | plugin test', function () { }), ); const addonDocsRootURL = '/my-addon/versions/main/'; - const actual = replaceAddonDocsRootURL( - chunk, - addonDocsRootURL, - encodedVersion, - ); + const actual = replaceDeployTokens(chunk, addonDocsRootURL, encodedVersion); const expected = '(e.children=[]),e),o.p="/my-addon/versions/main/assets/",(()=>{var e={143:0}'; assert.equal(actual, expected); }); + + it('replaceDeployTokens handles legacy ADDON_DOCS_ROOT_URL token', function () { + const contents = ` + + + `; + const encodedVersion = encodeURIComponent(JSON.stringify({ path: '' })); + const addonDocsRootURL = '/my-addon/'; + const actual = replaceDeployTokens( + contents, + addonDocsRootURL, + encodedVersion, + ); + + assert.include(actual, '%2Fmy-addon%2F'); + assert.include(actual, 'src="/my-addon/assets/vendor.js"'); + }); + + it('replaceDeployTokens with root-level deploy (addonDocsRootURL=/)', function () { + const contents = ` + + + `; + const encodedVersion = encodeURIComponent(JSON.stringify({ path: '' })); + const addonDocsRootURL = '/'; + const actual = replaceDeployTokens( + contents, + addonDocsRootURL, + encodedVersion, + ); + + // rootURL should stay as / (no change for root deploy) + assert.include(actual, '%22rootURL%22%3A%22%2F%22'); + // asset paths should stay as /assets/ (no change for root deploy) + assert.include(actual, 'src="/assets/vendor.js"'); + }); + + it('replaceDeployTokens with link href attributes', function () { + const contents = ''; + const encodedVersion = encodeURIComponent(JSON.stringify({ path: '' })); + const addonDocsRootURL = '/my-addon/'; + const actual = replaceDeployTokens( + contents, + addonDocsRootURL, + encodedVersion, + ); + + assert.include(actual, 'href="/my-addon/assets/vendor.css"'); + }); + + it('replaceDeployTokens replaces ADDON_DOCS_DEPLOY_VERSION', function () { + const contents = '{"deployVersion":"%22ADDON_DOCS_DEPLOY_VERSION%22"}'; + const version = { + path: 'versions/main', + name: 'main', + sha: 'abc', + tag: null, + key: 'main', + }; + const encodedVersion = encodeURIComponent(JSON.stringify(version)); + const actual = replaceDeployTokens(contents, '/', encodedVersion); + + assert.include(actual, encodedVersion); + assert.notInclude(actual, 'ADDON_DOCS_DEPLOY_VERSION'); + }); }); diff --git a/tests-node/unit/prember-urls-test.js b/tests-node/unit/prember-urls-test.js new file mode 100644 index 000000000..7ce504b94 --- /dev/null +++ b/tests-node/unit/prember-urls-test.js @@ -0,0 +1,264 @@ +'use strict'; + +const assert = require('chai').assert; +const path = require('path'); +const fs = require('fs-extra'); +const os = require('os'); +const premberUrls = require('../../lib/prember-urls'); + +describe('Unit | prember-urls', function () { + let tmpDir; + + beforeEach(function () { + tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'prember-urls-')); + }); + + afterEach(function () { + fs.removeSync(tmpDir); + }); + + it('returns ["/"] when distDir has no docs or search index', function () { + let urls = premberUrls({ distDir: tmpDir }); + assert.deepEqual(urls, ['/']); + }); + + it('discovers API pages from docs JSON navigationIndex', function () { + fs.ensureDirSync(path.join(tmpDir, 'docs')); + fs.writeJsonSync(path.join(tmpDir, 'docs', 'my-addon.json'), { + data: { + id: 'my-addon', + type: 'project', + attributes: { + navigationIndex: [ + { + type: 'components', + items: [ + { id: 'my-addon/components/foo', path: 'components/foo' }, + { + id: 'my-addon/components/bar', + path: 'components/bar', + }, + ], + }, + { + type: 'helpers', + items: [ + { + id: 'my-addon/helpers/baz', + path: 'helpers/baz', + }, + ], + }, + ], + }, + }, + }); + + let urls = premberUrls({ distDir: tmpDir }); + assert.include(urls, '/'); + assert.include(urls, '/docs'); + assert.include(urls, '/docs/api/components/foo'); + assert.include(urls, '/docs/api/components/bar'); + assert.include(urls, '/docs/api/helpers/baz'); + }); + + it('discovers template pages from search index', function () { + fs.ensureDirSync(path.join(tmpDir, 'ember-cli-addon-docs')); + fs.writeJsonSync( + path.join(tmpDir, 'ember-cli-addon-docs', 'search-index.json'), + { + index: {}, + documents: { + 'template:docs.quickstart': { + type: 'template', + route: 'docs.quickstart', + title: 'Quickstart', + }, + 'template:docs.usage': { + type: 'template', + route: 'docs.usage', + title: 'Usage', + }, + 'template:docs.components.index': { + type: 'template', + route: 'docs.components.index', + title: 'Components', + }, + }, + }, + ); + + let urls = premberUrls({ distDir: tmpDir }); + assert.include(urls, '/docs/quickstart'); + assert.include(urls, '/docs/usage'); + // docs.components.index should become /docs/components (strip trailing /index) + assert.include(urls, '/docs/components'); + assert.notInclude(urls, '/docs/components/index'); + }); + + it('strips trailing /index from routes', function () { + fs.ensureDirSync(path.join(tmpDir, 'ember-cli-addon-docs')); + fs.writeJsonSync( + path.join(tmpDir, 'ember-cli-addon-docs', 'search-index.json'), + { + index: {}, + documents: { + 'template:docs.index': { + type: 'template', + route: 'docs.index', + }, + 'template:sandbox.docs.index': { + type: 'template', + route: 'sandbox.docs.index', + }, + 'template:index': { + type: 'template', + route: 'index', + }, + }, + }, + ); + + let urls = premberUrls({ distDir: tmpDir }); + assert.include(urls, '/docs'); + assert.include(urls, '/sandbox/docs'); + assert.include(urls, '/'); + assert.notInclude(urls, '/docs/index'); + assert.notInclude(urls, '/sandbox/docs/index'); + assert.notInclude(urls, '/index'); + }); + + it('filters out internal routes from search index', function () { + fs.ensureDirSync(path.join(tmpDir, 'ember-cli-addon-docs')); + fs.writeJsonSync( + path.join(tmpDir, 'ember-cli-addon-docs', 'search-index.json'), + { + index: {}, + documents: { + 'template:application': { + type: 'template', + route: 'application', + }, + 'template:not-found': { + type: 'template', + route: 'not-found', + }, + 'template:templates.docs.foo': { + type: 'template', + route: 'templates.docs.foo', + }, + 'template:pods.sandbox.template': { + type: 'template', + route: 'pods.sandbox.template', + }, + 'template:docs.quickstart': { + type: 'template', + route: 'docs.quickstart', + }, + }, + }, + ); + + let urls = premberUrls({ distDir: tmpDir }); + assert.include(urls, '/docs/quickstart'); + assert.notInclude(urls, '/application'); + assert.notInclude(urls, '/not-found'); + assert.notInclude(urls, '/templates/docs/foo'); + assert.notInclude(urls, '/pods/sandbox/template'); + }); + + it('skips non-template documents in search index', function () { + fs.ensureDirSync(path.join(tmpDir, 'ember-cli-addon-docs')); + fs.writeJsonSync( + path.join(tmpDir, 'ember-cli-addon-docs', 'search-index.json'), + { + index: {}, + documents: { + 'component:my-addon/components/foo': { + type: 'component', + title: 'Foo', + }, + 'module:my-addon/utils/bar': { + type: 'module', + title: 'bar', + }, + }, + }, + ); + + let urls = premberUrls({ distDir: tmpDir }); + assert.deepEqual(urls, ['/']); + }); + + it('only generates /docs/api/ URLs for the first project when multiple exist', function () { + fs.ensureDirSync(path.join(tmpDir, 'docs')); + + fs.writeJsonSync(path.join(tmpDir, 'docs', 'main-addon.json'), { + data: { + id: 'main-addon', + type: 'project', + attributes: { + navigationIndex: [ + { + type: 'components', + items: [{ id: 'main/foo', path: 'components/foo' }], + }, + ], + }, + }, + }); + + fs.writeJsonSync(path.join(tmpDir, 'docs', 'sandbox.json'), { + data: { + id: 'sandbox', + type: 'project', + attributes: { + navigationIndex: [ + { + type: 'components', + items: [{ id: 'sandbox/bar', path: 'components/bar' }], + }, + ], + }, + }, + }); + + let urls = premberUrls({ distDir: tmpDir }); + assert.include(urls, '/docs/api/components/foo'); + // sandbox URLs should not be generated as /docs/api/ + assert.notInclude(urls, '/docs/api/components/bar'); + }); + + it('deduplicates URLs', function () { + fs.ensureDirSync(path.join(tmpDir, 'docs')); + fs.ensureDirSync(path.join(tmpDir, 'ember-cli-addon-docs')); + + fs.writeJsonSync(path.join(tmpDir, 'docs', 'my-addon.json'), { + data: { + id: 'my-addon', + type: 'project', + attributes: { navigationIndex: [] }, + }, + }); + + fs.writeJsonSync( + path.join(tmpDir, 'ember-cli-addon-docs', 'search-index.json'), + { + index: {}, + documents: { + 'template:docs.index': { + type: 'template', + route: 'docs.index', + }, + }, + }, + ); + + let urls = premberUrls({ distDir: tmpDir }); + let docsCount = urls.filter((u) => u === '/docs').length; + // Both the docs JSON and the search index contribute '/docs', but the URL + // itself should only appear once after deduplication. + assert.equal(docsCount, 1); + assert.notInclude(urls, '/docs/index'); + }); +}); diff --git a/tests/dummy/app/templates/docs/build-options.md b/tests/dummy/app/templates/docs/build-options.md index 029f29ec8..d6597bde0 100644 --- a/tests/dummy/app/templates/docs/build-options.md +++ b/tests/dummy/app/templates/docs/build-options.md @@ -2,6 +2,31 @@ To override the default configuration of the addon, use the following options in your `ember-cli-build.js`. +## Static site generation (prember) + +AddonDocs uses [prember](https://github.com/ef4/prember) and [FastBoot](https://github.com/ember-fastboot/ember-cli-fastboot) to pre-render your documentation as static HTML. This gives you faster initial page loads and better SEO. + +Both `ember-cli-fastboot` and `prember` are installed automatically when you run `ember install ember-cli-addon-docs`. Prember is configured automatically — all your documentation pages (guides and API docs) are discovered from the build output and pre-rendered as static HTML files. No manual configuration is needed. + +If you need to customize which URLs are pre-rendered, you can override the prember config in your `ember-cli-build.js`: + +