From 743cc1f352b4eb8b444a8cf8dec10a1da2905414 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sun, 1 Nov 2020 13:18:51 +0100 Subject: [PATCH 1/4] docs/verify-links: validate links to docs from other parts of the microsite --- docs/verify-links.js | 82 ++++++++++++++++++++++++++++++++++---------- 1 file changed, 64 insertions(+), 18 deletions(-) diff --git a/docs/verify-links.js b/docs/verify-links.js index 9bf8a67ad8..442d43aa37 100755 --- a/docs/verify-links.js +++ b/docs/verify-links.js @@ -15,13 +15,13 @@ * limitations under the License. */ -const { resolve: resolvePath, dirname } = require('path'); +const { resolve: resolvePath, join: joinPath, dirname } = require('path'); const fs = require('fs-extra'); const recursive = require('recursive-readdir'); const projectRoot = resolvePath(__dirname, '..'); -async function verifyUrl(basePath, url) { +async function verifyUrl(basePath, url, docPages) { // Avoid having absolute URL links within docs/, so that links work on the site if ( url.match( @@ -29,7 +29,7 @@ async function verifyUrl(basePath, url) { ) && basePath.match(/^(?:docs|microsite)\//) ) { - return { url, basePath, problem: 'absolute' }; + return { url, basePath, problem: 'github' }; } url = url.replace(/#.*$/, ''); @@ -49,8 +49,16 @@ async function verifyUrl(basePath, url) { let path = ''; if (url.startsWith('/')) { - if (url.startsWith('/docs/') && basePath.match(/^(?:docs|microsite)\//)) { - return { url, basePath, problem: 'not-relative' }; + if (url.startsWith('/docs/')) { + if (basePath.match(/^(?:docs)\//)) { + return { url, basePath, problem: 'not-relative' }; + } + if (basePath.startsWith('microsite/')) { + if (docPages.has(url)) { + return; + } + return { url, basePath, problem: 'doc-missing' }; + } } const staticPath = resolvePath(projectRoot, 'microsite/static', `.${url}`); @@ -71,14 +79,14 @@ async function verifyUrl(basePath, url) { return; } -async function verifyFile(filePath) { +async function verifyFile(filePath, docPages) { const content = await fs.readFile(filePath, 'utf8'); const mdLinks = content.match(/\[.+?\]\(.+?\)/g) || []; const badUrls = []; for (const mdLink of mdLinks) { const url = mdLink.match(/\[.+\]\((.+)\)/)[1].trim(); - const badUrl = await verifyUrl(filePath, url); + const badUrl = await verifyUrl(filePath, url, docPages); if (badUrl) { badUrls.push(badUrl); } @@ -87,20 +95,46 @@ async function verifyFile(filePath) { return badUrls; } +// This discovers the doc paths as they will be available on the microsite. +// It is used to validate microsite links from outside /docs/, as those +// are not transformed from the markdown file representation by docusaurus. +async function findExternalDocsLinks(dir) { + const allFiles = await recursive(dir); + const mdFiles = allFiles.filter(p => p.endsWith('.md')); + + const paths = new Map(); + + for (const file of mdFiles) { + const content = await fs.readFile(file, 'utf8'); + const url = `/${file}`; + const match = content.match(/---(?:\r|\n|.)*^id: (.*)$/m); + + // Both docs with an id and without should remove trailing /index + const realPath = (match + ? joinPath(dirname(url), match[1]) + : url.replace(/\.md$/, '') + ).replace(/\/index$/, ''); + + paths.set(url, realPath); + if (url.endsWith('/index.md')) { + paths.set(url.replace(/\/index\.md$/, ''), realPath); + } + } + + return paths; +} + async function main() { process.chdir(projectRoot); - const files = await recursive('.', [ - 'node_modules', - 'dist', - 'bin', - 'microsite', - ]); + const files = await recursive('.', ['node_modules', 'dist', 'bin']); const mdFiles = files.filter(f => f.endsWith('.md')); const badUrls = []; + const docPages = await findExternalDocsLinks('docs'); + for (const mdFile of mdFiles) { - const badFileUrls = await verifyFile(mdFile); + const badFileUrls = await verifyFile(mdFile, new Set(docPages.values())); badUrls.push(...badFileUrls); } @@ -111,12 +145,24 @@ async function main() { console.error( `Unable to reach ${url} from root or microsite/static/, linked from ${basePath}`, ); - } else if (problem === 'not-relative') { - console.error('Links to /docs/ must be relative'); + } else if (problem === 'doc-missing') { + const suggestion = + docPages.get(url) || + docPages.get(new URL(url, 'http://localhost').pathname); + console.error('Links into /docs/ must use an externally reachable ID'); console.error(` From: ${basePath}`); console.error(` To: ${url}`); - } else if (problem === 'absolute') { - console.error(`Link to docs/ should be replaced by a relative URL`); + if (suggestion) { + console.error(` Replace With: ${suggestion}`); + } + } else if (problem === 'not-relative') { + console.error('Links within /docs/ must be relative'); + console.error(` From: ${basePath}`); + console.error(` To: ${url}`); + } else if (problem === 'github') { + console.error( + `Link to docs/ should not use a GitHub URL, use a relative URL instead`, + ); console.error(` From: ${basePath}`); console.error(` To: ${url}`); } From 117c621e966a633431b8447e63f62bb91847c744 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sun, 1 Nov 2020 13:19:04 +0100 Subject: [PATCH 2/4] microsite/blog: fix broken links --- microsite/blog/2020-05-22-phase-2-service-catalog.md | 2 +- microsite/blog/2020-06-22-backstage-service-catalog-alpha.md | 2 +- ...-how-to-enable-authentication-in-backstage-using-passport.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/microsite/blog/2020-05-22-phase-2-service-catalog.md b/microsite/blog/2020-05-22-phase-2-service-catalog.md index 682965d2dd..5c988c03aa 100644 --- a/microsite/blog/2020-05-22-phase-2-service-catalog.md +++ b/microsite/blog/2020-05-22-phase-2-service-catalog.md @@ -27,7 +27,7 @@ Quote from [Platform Nuts & Bolts: Extendable Data Models](https://www.kislayver Entities, or what we refer to as “components” in Backstage, represent all software, including services, websites, libraries, data pipelines, and so forth. The focus of Phase 2 will be on adding an entity model in Backstage that makes it easy for engineers to create and manage the software components they own. -With the ability to create a plethora of components in Backstage, how does one keep track of all the software in the ecosystem? Therein lies the highlight feature of Phase 2: the [Service Catalog](https://github.com/spotify/backstage/milestone/4). The service catalog — or software catalog — is a centralized system that keeps track of ownership and metadata about all software in your ecosystem. The catalog is built around the concept of [metadata yaml files](/docs/architecture-decisions/adr002-default-catalog-file-format.md) stored together with the code, which are then harvested and visualized in Backstage. +With the ability to create a plethora of components in Backstage, how does one keep track of all the software in the ecosystem? Therein lies the highlight feature of Phase 2: the [Service Catalog](https://github.com/spotify/backstage/milestone/4). The service catalog — or software catalog — is a centralized system that keeps track of ownership and metadata about all software in your ecosystem. The catalog is built around the concept of [metadata yaml files](/docs/architecture-decisions/adrs-adr002) stored together with the code, which are then harvested and visualized in Backstage. ![img](assets/20-05-20/Service_Catalog_MVP.png) diff --git a/microsite/blog/2020-06-22-backstage-service-catalog-alpha.md b/microsite/blog/2020-06-22-backstage-service-catalog-alpha.md index 0928b8769b..167cc753f8 100644 --- a/microsite/blog/2020-06-22-backstage-service-catalog-alpha.md +++ b/microsite/blog/2020-06-22-backstage-service-catalog-alpha.md @@ -24,7 +24,7 @@ With these insights we decided to re-focus our efforts towards the most requeste ## What is the service catalog? -The Backstage Service Catalog — actually, a software catalog, since it includes more than just services — is a centralized system that keeps track of ownership and metadata for all the software in your ecosystem (services, websites, libraries, data pipelines, etc). The catalog is built around the concept of [metadata yaml files](/docs/architecture-decisions/adr002-default-catalog-file-format.md#format) stored together with the code, which are then harvested and visualized in Backstage. +The Backstage Service Catalog — actually, a software catalog, since it includes more than just services — is a centralized system that keeps track of ownership and metadata for all the software in your ecosystem (services, websites, libraries, data pipelines, etc). The catalog is built around the concept of [metadata yaml files](/docs/architecture-decisions/adrs-adr002) stored together with the code, which are then harvested and visualized in Backstage. This was our pitch for the virtues of a service catalog when we first [announced](https://backstage.io/blog/2020/05/22/phase-2-service-catalog) it as part of Phase 2: diff --git a/microsite/blog/2020-07-01-how-to-enable-authentication-in-backstage-using-passport.md b/microsite/blog/2020-07-01-how-to-enable-authentication-in-backstage-using-passport.md index ccdd8b34c6..b2a944bf26 100644 --- a/microsite/blog/2020-07-01-how-to-enable-authentication-in-backstage-using-passport.md +++ b/microsite/blog/2020-07-01-how-to-enable-authentication-in-backstage-using-passport.md @@ -39,7 +39,7 @@ Getting started is really straightforward, and can be broadly broken down into f 4. Add the provider to the backend. 5. Add a frontend Auth Utility API. -For full details, take a look at our [“Adding authentication providers” documentation](/docs/auth/add-auth-provider.md) and at the [excellent documentation](http://www.passportjs.org/docs/) provided by Passport. +For full details, take a look at our [“Adding authentication providers” documentation](/docs/auth/add-auth-provider) and at the [excellent documentation](http://www.passportjs.org/docs/) provided by Passport. ## Interested in contributing to the next steps for authentication? From ed3ff8f8e3fdcfc798927531ed1102ce3e6b650b Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sun, 1 Nov 2020 13:25:38 +0100 Subject: [PATCH 3/4] docs: moved verify-links to scripts --- .github/workflows/ci.yml | 2 +- {docs => scripts}/verify-links.js | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename {docs => scripts}/verify-links.js (100%) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d00f304b58..75c6a13cd2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -67,7 +67,7 @@ jobs: continue-on-error: true - name: verify doc links - run: node docs/verify-links.js + run: node scripts/verify-links.js - name: prettier run: yarn prettier:check diff --git a/docs/verify-links.js b/scripts/verify-links.js similarity index 100% rename from docs/verify-links.js rename to scripts/verify-links.js From b723cd620cc4b66e6ee6b6bcd01feb531905ce05 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sun, 1 Nov 2020 13:28:25 +0100 Subject: [PATCH 4/4] scripts/verify-links: fix lint issues --- scripts/verify-links.js | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/scripts/verify-links.js b/scripts/verify-links.js index 442d43aa37..6126f693e4 100755 --- a/scripts/verify-links.js +++ b/scripts/verify-links.js @@ -15,35 +15,38 @@ * limitations under the License. */ +/* eslint-disable import/no-extraneous-dependencies */ + const { resolve: resolvePath, join: joinPath, dirname } = require('path'); const fs = require('fs-extra'); const recursive = require('recursive-readdir'); const projectRoot = resolvePath(__dirname, '..'); -async function verifyUrl(basePath, url, docPages) { +async function verifyUrl(basePath, absUrl, docPages) { // Avoid having absolute URL links within docs/, so that links work on the site if ( - url.match( + absUrl.match( /https:\/\/github.com\/spotify\/backstage\/(tree|blob)\/master\/docs\//, ) && basePath.match(/^(?:docs|microsite)\//) ) { - return { url, basePath, problem: 'github' }; + return { url: absUrl, basePath, problem: 'github' }; } - url = url.replace(/#.*$/, ''); - url = url.replace( - /https:\/\/github.com\/spotify\/backstage\/(tree|blob)\/master/, - '', - ); + const url = absUrl + .replace(/#.*$/, '') + .replace( + /https:\/\/github.com\/spotify\/backstage\/(tree|blob)\/master/, + '', + ); if (!url) { - return; + return undefined; } // Only verify existence of local files for now, so skip anything with a schema if (url.match(/[a-z]+:/)) { - return; + return undefined; } let path = ''; @@ -55,7 +58,7 @@ async function verifyUrl(basePath, url, docPages) { } if (basePath.startsWith('microsite/')) { if (docPages.has(url)) { - return; + return undefined; } return { url, basePath, problem: 'doc-missing' }; } @@ -63,7 +66,7 @@ async function verifyUrl(basePath, url, docPages) { const staticPath = resolvePath(projectRoot, 'microsite/static', `.${url}`); if (await fs.pathExists(staticPath)) { - return; + return undefined; } path = resolvePath(projectRoot, `.${url}`); @@ -76,7 +79,7 @@ async function verifyUrl(basePath, url, docPages) { return { url, basePath, problem: 'missing' }; } - return; + return undefined; } async function verifyFile(filePath, docPages) {