diff --git a/.eslintrc.js b/.eslintrc.js index a34c6390..083c799b 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -44,6 +44,7 @@ module.exports = { 'lib/**/*.js', 'bin/*', 'server/**/*.js', + 'prember-urls.js', ], excludedFiles: ['config/deprecation-workflow.js'], parserOptions: { diff --git a/.gitignore b/.gitignore index 4a8e5ae0..982c7aca 100644 --- a/.gitignore +++ b/.gitignore @@ -40,3 +40,5 @@ local.log /.node_modules.ember-try/ /bower.json.ember-try /package.json.ember-try + +/ember-api-docs-data/ diff --git a/app/adapters/application.js b/app/adapters/application.js index 684c900f..cb338e22 100644 --- a/app/adapters/application.js +++ b/app/adapters/application.js @@ -6,7 +6,7 @@ import { pluralize } from 'ember-inflector'; import { isBlank } from '@ember/utils'; export default class Application extends JSONAPIAdapter { - host = ENV.API_HOST; + // host = ENV.API_HOST; currentProject = ''; currentProjectVersion = ''; @@ -47,7 +47,7 @@ export default class Application extends JSONAPIAdapter { async findRecord(store, { modelName }, id) { let url; - let host = this.host; + // let host = this.host; let projectName = this.currentProject; if (['namespace', 'class', 'module'].indexOf(modelName) > -1) { @@ -85,7 +85,7 @@ export default class Application extends JSONAPIAdapter { throw new Error('Unexpected model lookup'); } - url = `${host}/${url}.json`; + url = `/${url}.json`; let response = await fetch(url); let json = await response.json(); diff --git a/app/router.js b/app/router.js index 81350a3b..8edfb5dc 100644 --- a/app/router.js +++ b/app/router.js @@ -92,4 +92,39 @@ AppRouter.map(function () { this.route('data-module', { path: '/data/modules/:module' }); }); +/* +404 +ember-cli +project + +/:project/:project_version + /classes/:class + /methods, /properties, /events + /functions/:module (no sub routes) + /namespaces/:namespace + /methods, /properties, /events + /modules/:module + /methods, /properties, /events + +SUB ROUTES + +Instead of https://api.emberjs.com/ember/4.6/classes/Engine/methods/unregister?anchor=unregister +We can do https://api.emberjs.com/ember/4.6/classes/Engine/methods?anchor=unregister + + /methods/:method + /properties/:property + /events/:event + +OTHER STATES +private, deprecated, inherited, protected +inherited is not reflected in URL state but it's checked by default + +MAYBE REDIRECTS + +/data/modules/:module +/data/classes/:class +/modules/:module +/classes/:class +*/ + export default AppRouter; diff --git a/app/services/meta-store.js b/app/services/meta-store.js index 85f1167d..ceabe895 100644 --- a/app/services/meta-store.js +++ b/app/services/meta-store.js @@ -22,7 +22,7 @@ export default class MetaStoreService extends Service { } getRevId(project, version, type, id) { - let encodedId = encodeURIComponent(id); + let encodedId = id; return this.projectRevMap[`${project}-${version}`][type][encodedId]; } diff --git a/ember-cli-build.js b/ember-cli-build.js index 280943f4..f2703604 100644 --- a/ember-cli-build.js +++ b/ember-cli-build.js @@ -4,9 +4,13 @@ const EmberApp = require('ember-cli/lib/broccoli/ember-app'); const Funnel = require('broccoli-funnel'); const mergeTrees = require('broccoli-merge-trees'); const envIsProduction = process.env.EMBER_ENV === 'production'; +const premberUrls = require('./prember-urls'); module.exports = function (defaults) { let app = new EmberApp(defaults, { + prember: { + urls: premberUrls(), + }, fingerprint: { extensions: [ 'js', @@ -63,5 +67,6 @@ module.exports = function (defaults) { include: ['mappings.json'], destDir: '/assets/', }); + return mergeTrees([app.toTree(), mappingsTree]); }; diff --git a/lib/api-docs-data/index.js b/lib/api-docs-data/index.js new file mode 100644 index 00000000..ef19f698 --- /dev/null +++ b/lib/api-docs-data/index.js @@ -0,0 +1,17 @@ +'use strict'; + +const Funnel = require('broccoli-funnel'); + +module.exports = { + name: require('./package').name, + + isDevelopingAddon() { + return true; + }, + + treeForPublic() { + return new Funnel('ember-api-docs-data', { + include: ['**/json-docs/**', '**/rev-index/**'], + }); + }, +}; diff --git a/lib/api-docs-data/package.json b/lib/api-docs-data/package.json new file mode 100644 index 00000000..9b312d9f --- /dev/null +++ b/lib/api-docs-data/package.json @@ -0,0 +1,9 @@ +{ + "name": "api-docs-data", + "keywords": [ + "ember-addon" + ], + "peerDependencies": { + "broccoli-funnel": "*" + } +} diff --git a/package-lock.json b/package-lock.json index 74df2866..794972d1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -108,6 +108,7 @@ "minimist": "^1.2.6", "normalize.css": "^8.0.1", "npm-run-all": "^4.1.5", + "prember": "^1.1.1", "prettier": "^2.2.1", "qunit": "^2.16.0", "qunit-dom": "^1.6.0", @@ -11859,6 +11860,12 @@ "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", "dev": true }, + "node_modules/denodeify": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/denodeify/-/denodeify-1.2.1.tgz", + "integrity": "sha512-KNTihKNmQENUZeKu5fzfpzRqR5S2VMp4gl9RFHiWzj9DfvYQPMJ6XHKNaQxaGCXwPk6y9yme3aUoaiAe+KX+vg==", + "dev": true + }, "node_modules/depd": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", @@ -33117,6 +33124,100 @@ "node": ">= 0.8.0" } }, + "node_modules/prember": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/prember/-/prember-1.1.1.tgz", + "integrity": "sha512-7lG4eNDKp0Udh2PCxibxMSkDFhscD0FGNGwASk5wUhHayz0R12AZqMxcbveDEMHI9smhYoymiFEdT5A3W7vIpw==", + "dev": true, + "dependencies": { + "broccoli-debug": "^0.6.3", + "broccoli-merge-trees": "^2.0.0", + "broccoli-plugin": "^1.3.0", + "chalk": "^2.3.0", + "denodeify": "^1.2.1", + "ember-cli-babel": "^7.26.6", + "express": "^4.16.2", + "fastboot": "^3.1.2", + "mkdirp": "^0.5.1" + }, + "engines": { + "node": "12.* || 14.* || >= 16" + } + }, + "node_modules/prember/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/prember/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/prember/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/prember/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/prember/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/prember/node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/prember/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/prettier": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.3.2.tgz", @@ -48621,6 +48722,12 @@ "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", "dev": true }, + "denodeify": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/denodeify/-/denodeify-1.2.1.tgz", + "integrity": "sha512-KNTihKNmQENUZeKu5fzfpzRqR5S2VMp4gl9RFHiWzj9DfvYQPMJ6XHKNaQxaGCXwPk6y9yme3aUoaiAe+KX+vg==", + "dev": true + }, "depd": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", @@ -65943,6 +66050,84 @@ "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", "dev": true }, + "prember": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/prember/-/prember-1.1.1.tgz", + "integrity": "sha512-7lG4eNDKp0Udh2PCxibxMSkDFhscD0FGNGwASk5wUhHayz0R12AZqMxcbveDEMHI9smhYoymiFEdT5A3W7vIpw==", + "dev": true, + "requires": { + "broccoli-debug": "^0.6.3", + "broccoli-merge-trees": "^2.0.0", + "broccoli-plugin": "^1.3.0", + "chalk": "^2.3.0", + "denodeify": "^1.2.1", + "ember-cli-babel": "^7.26.6", + "express": "^4.16.2", + "fastboot": "^3.1.2", + "mkdirp": "^0.5.1" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true + }, + "mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "requires": { + "minimist": "^1.2.6" + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, "prettier": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.3.2.tgz", diff --git a/package.json b/package.json index 2806adc7..05e96652 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "test": "tests" }, "scripts": { + "clone": "rm -rf ember-api-docs-data && git clone --depth 1 https://github.com/ember-learn/ember-api-docs-data.git -b fix-files", "build": "ember build --environment=production", "lint": "npm-run-all --aggregate-output --continue-on-error --parallel 'lint:!(fix)'", "lint:fix": "npm-run-all --aggregate-output --continue-on-error --parallel lint:*:fix", @@ -125,6 +126,7 @@ "minimist": "^1.2.6", "normalize.css": "^8.0.1", "npm-run-all": "^4.1.5", + "prember": "^1.1.1", "prettier": "^2.2.1", "qunit": "^2.16.0", "qunit-dom": "^1.6.0", @@ -154,5 +156,10 @@ "volta": { "node": "14.17.0", "npm": "8.19.3" + }, + "ember-addon": { + "paths": [ + "lib/api-docs-data" + ] } } diff --git a/prember-urls.js b/prember-urls.js new file mode 100644 index 00000000..54d51242 --- /dev/null +++ b/prember-urls.js @@ -0,0 +1,139 @@ +const { readdirSync, readFileSync } = require('fs'); +const cmp = require('semver-compare'); +const semver = require('semver'); + +function partialUrlEncode(input) { + return input.replace('/', '%2F'); +} + +module.exports = function () { + const projects = readdirSync('ember-api-docs-data/json-docs'); + + const urls = []; + + projects.forEach((p) => { + // add release for each of the projects + urls.push(`/${p}/release`); + + const fullProjectVersions = readdirSync( + `ember-api-docs-data/json-docs/${p}` + ).filter((v) => v.match(/\d+\.\d+\.\d+/)); + + // add landing page for each of the projects versions + const projectVersions = fullProjectVersions.map((v) => { + let [, major, minor] = v.match(/(\d+)\.(\d+)\.\d+/); + return `${major}.${minor}`; + }); // uniq + + const uniqueProjectVersions = [...new Set(projectVersions)]; + + const oldVersions = [ + '1.12', + '1.13', + '2.17', + '2.18', + '3.4', + '3.8', + '3.12', + '3.16', + '3.20', + '3.24', + '3.27', + '3.28', + ]; + + uniqueProjectVersions.forEach((uniqVersion) => { + if ( + !oldVersions.includes(uniqVersion) && + !semver.gte(`${uniqVersion}.0`, '4.0.0') + ) { + return; + } + + urls.push(`/${p}/${uniqVersion}`); + + const sortedPatchVersions = fullProjectVersions + .filter((projectVersion) => { + // console.log("comparing", projectVersion, uniqVersion, semver.satisfies(projectVersion, uniqVersion)) + return semver.satisfies(projectVersion, uniqVersion); + }) + .sort(cmp); + + const highestPatchVersion = + sortedPatchVersions[sortedPatchVersions.length - 1]; + + const revIndex = require(`${__dirname}/ember-api-docs-data/rev-index/${p}-${highestPatchVersion}.json`); + + ['classes', 'namespaces', 'modules'].forEach((entity) => { + // add classes + revIndex.data.relationships[entity].data.forEach(({ id }) => { + const [, cleanId] = id.match(/^.+-\d+\.\d+\.\d+-(.*)/); + urls.push( + `/${p}/${uniqVersion}/${entity}/${partialUrlEncode(cleanId)}` + ); + + // TODO only include sub routes if that entity has stuff in that route i.e. if it's empty don't pre-render it + urls.push( + `/${p}/${uniqVersion}/${entity}/${partialUrlEncode( + cleanId + )}/methods` + ); + urls.push( + `/${p}/${uniqVersion}/${entity}/${partialUrlEncode( + cleanId + )}/properties` + ); + urls.push( + `/${p}/${uniqVersion}/${entity}/${partialUrlEncode(cleanId)}/events` + ); + + if (entity === 'modules') { + const moduleKey = id; + + const fileName = revIndex.meta.module[moduleKey]; + + if (fileName === undefined) { + // rare cases when very strange things make it through this far + // e.g. ember-3.0.0-ember%0A%0ARemove%20after%203.4%20once%20_ENABLE_RENDER_SUPPORT%20flag%20is%20no%20longer%20needed. + // 🤷‍♀️ + return; + } + + const moduleData = require(`${__dirname}/ember-api-docs-data/json-docs/${p}/${highestPatchVersion}/modules/${fileName}.json`); + + const staticFunctions = moduleData.data.attributes.staticfunctions; + + Object.keys(staticFunctions).forEach((k) => { + const listOfFunctions = staticFunctions[k]; + + listOfFunctions.forEach((func) => { + urls.push( + `/${p}/${uniqVersion}/functions/${encodeURIComponent( + func.class + )}/${func.name}` + ); + }); + }); + } + + // TODO review that we have got all the URLs that we care about + + // TODO discuss only prembering "supported" versions - maybe last version in a major and supported versions + // alternative is to rely on netlify complex build + }); + }); + }); + }); + + return urls; +}; + +// this is useful to debug why a url isn't being prembered +// DEBUG=prember-urls node prember-urls.js +if (process.env.DEBUG === 'prember-urls') { + let urls = module.exports(); + + urls.forEach((url) => console.log(url)); + + console.log(`\n${urls.length} total URLs`); +}