-
-
Notifications
You must be signed in to change notification settings - Fork 141
Add fastboot/prember #1691
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add fastboot/prember #1691
Changes from all commits
4d0ac4c
1d43d2d
bbaa25e
7db0cf3
53d5e4d
e08e25d
1442f5e
8d3f882
a790176
b63d962
8012ae2
c2db1fe
6ba32f4
2c092c2
c02e003
b571ab9
16ca8ce
04201f1
7f0f1b4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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, | ||
| }, | ||
|
Comment on lines
+21
to
+23
|
||
| ]; | ||
| 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}`; | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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); | ||
| } | ||
|
RobbieTheWagner marked this conversation as resolved.
|
||
| } | ||
| } | ||
|
|
||
| // 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'); | ||
|
RobbieTheWagner marked this conversation as resolved.
|
||
|
|
||
| for (let section of navIndex) { | ||
| for (let item of section.items) { | ||
| urls.add(`/docs/api/${item.path}`); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| return [...urls]; | ||
| }; | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The FastBoot-specific
_fetchProjectpath (Nodehttprequest + header-derived origin) isn’t covered by the current unit tests. Adding a test that stubsservice:fastbootandFastBoot.require('http')would help prevent regressions in the static prerendering path.