From 4956dfdb5bfa9470a0210299ac169d2ac06617f8 Mon Sep 17 00:00:00 2001 From: Heather Date: Tue, 7 May 2024 16:47:07 -0400 Subject: [PATCH 1/8] use cheerio to parse sitemap URLs --- .github/workflows/accessibility_test.yml | 45 +++++++++++++ .../scripts/check_for_changed_pages.js | 63 +++++++++++++++++++ .github/workflows/scripts/run_axe.js | 38 +++++++++++ package.json | 3 + src/pages/_app.tsx | 3 + src/utils/accessibilityScanner.ts | 10 +++ yarn.lock | 58 +++++++++++++++++ 7 files changed, 220 insertions(+) create mode 100644 .github/workflows/accessibility_test.yml create mode 100644 .github/workflows/scripts/check_for_changed_pages.js create mode 100644 .github/workflows/scripts/run_axe.js create mode 100644 src/utils/accessibilityScanner.ts diff --git a/.github/workflows/accessibility_test.yml b/.github/workflows/accessibility_test.yml new file mode 100644 index 00000000000..f05edcd9cda --- /dev/null +++ b/.github/workflows/accessibility_test.yml @@ -0,0 +1,45 @@ +name: Accessibility +on: + pull_request: + branches: [main] + types: [opened, synchronize] +env: + BUILD_DIR: 'client/www/next-build' +jobs: + accessibility: + name: Runs axe accessibility testing on changed pages + runs-on: ubuntu-latest + steps: + - name: Checkout branch + uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 https://github.com/actions/checkout/commit/9bb56186c3b09b4f86b1c65136769dd318469633 + - name: Setup Node.js 20 + uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # 4.0.2 https://github.com/actions/setup-node/releases/tag/v4.0.2 + with: + node-version: 20.x + - name: Install dependencies + run: yarn + - name: Build + run: yarn build + env: + NODE_OPTIONS: --max_old_space_size=4096 + - name: Get changed/new pages to run accessibility tests on + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 https://github.com/actions/github-script/commit/60a0d83039c74a4aee543508d2ffcb1c3799cdea + id: pages-to-a11y-test + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { getChangedPages } = require('./.github/workflows/scripts/check_for_changed_pages.js'); + return getChangedPages({github, context}); + - name: Run site + run: | + python -m http.server 3000 -d ${{ env.BUILD_DIR }} & + sleep 5 + - name: Run accessibility tests on changed/new MDX pages + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 https://github.com/actions/github-script/commit/60a0d83039c74a4aee543508d2ffcb1c3799cdea + id: axeResults + with: + result-encoding: string + script: | + const { runAxe } = require('./.github/workflows/scripts/run_axe.js'); + const pages = ${{ steps.pages-to-a11y-test.outputs.result }} + return await runAxe(pages) diff --git a/.github/workflows/scripts/check_for_changed_pages.js b/.github/workflows/scripts/check_for_changed_pages.js new file mode 100644 index 00000000000..0533a13ac50 --- /dev/null +++ b/.github/workflows/scripts/check_for_changed_pages.js @@ -0,0 +1,63 @@ +module.exports = { + getChangedPages: ({ github, context }) => { + const fs = require('fs'); + const cheerio = require('cheerio'); + + const urlList = []; + + const { + issue: { number: issue_number }, + repo: { owner, repo } + } = context; + + // Use the Github API to query for the list of files from the PR + return github + .paginate( + 'GET /repos/{owner}/{repo}/pulls/{pull_number}/files', + { owner, repo, pull_number: issue_number }, + (response) => response.data.filter((file) => (file.status === 'modified' || file.status === 'added')) + ) + .then((files) => { + const possiblePages = []; + const platforms = [ + 'android', + 'angular', + 'flutter', + 'javascript', + 'nextjs', + 'react', + 'react-native', + 'swift', + 'vue', + ] + files.forEach(({filename}) => { + const isPage = filename.startsWith('src/pages') && (filename.endsWith('index.mdx') || filename.endsWith('index.tsx')); + if(isPage) { + + const path = filename.replace('src/pages', '').replace('/index.mdx', '').replace('/index.tsx', ''); + if(path.includes('[platform]')) { + platforms.forEach((platform) => { + possiblePages.push(path.replace('[platform]', platform)); + }) + } else { + possiblePages.push(path); + } + } + }); + + const siteMap = fs.readFileSync('public/sitemap.xml'); + + const siteMapParse = cheerio.load(siteMap, { + xml: true + }); + + siteMapParse('url').each(function () { + urlList.push(siteMapParse(this).find('loc').text()); + }); + + const pages = possiblePages.filter((page) => urlList.includes(`https://docs.amplify.aws${page}/`)); + + return pages; + }); + }, +} diff --git a/.github/workflows/scripts/run_axe.js b/.github/workflows/scripts/run_axe.js new file mode 100644 index 00000000000..1437ba96524 --- /dev/null +++ b/.github/workflows/scripts/run_axe.js @@ -0,0 +1,38 @@ +module.exports = { + runAxe: (pages) => { + const core = require('@actions/core'); + const { AxePuppeteer } = require('@axe-core/puppeteer'); + const puppeteer = require('puppeteer'); + + const violations = []; + + async function runAxeAnalyze(pages) { + for (const page of pages) { + console.log(`testing page http://localhost:3000${page}/`); + const browser = await puppeteer.launch(); + const pageToVisit = await browser.newPage(); + await pageToVisit.goto(`http://localhost:3000${page}/`); + try { + const results = await new AxePuppeteer(pageToVisit).analyze(); + if(results.violations) { + results.violations.forEach(violation => { + console.log(violation); + violations.push(violation); + }) + } else { + console.log('No violations found.'); + } + + } catch (e) { + // do something with the error + } + await browser.close(); + } + if(violations.length > 0) { + core.setFailed(`Please fix the above accessibility violations.`); + } + } + + runAxeAnalyze(pages); + } +}; diff --git a/package.json b/package.json index b5d0104eaf5..47cb3072e78 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,9 @@ "react-icons": "^4.7.1" }, "devDependencies": { + "@actions/core": "^1.10.1", + "@axe-core/puppeteer": "^4.9.0", + "@axe-core/react": "^4.9.0", "@mdx-js/loader": "^2.3.0", "@mdx-js/mdx": "^2.3.0", "@mdx-js/react": "^2.3.0", diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index fbf461c76be..022c6e2cf93 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -6,6 +6,7 @@ import { Layout } from '@/components/Layout'; import { useRouter } from 'next/router'; import { useEffect } from 'react'; import { trackPageVisit } from '../utils/track'; +import { accessibilityScanner } from '@/utils/accessibilityScanner'; function MyApp({ Component, pageProps }) { const { @@ -186,4 +187,6 @@ function MyApp({ Component, pageProps }) { ); } +accessibilityScanner(MyApp); + export default MyApp; diff --git a/src/utils/accessibilityScanner.ts b/src/utils/accessibilityScanner.ts new file mode 100644 index 00000000000..02c8a2a05f9 --- /dev/null +++ b/src/utils/accessibilityScanner.ts @@ -0,0 +1,10 @@ +export const accessibilityScanner = async ( + App, + config?: Record +): Promise => { + if (typeof window !== 'undefined' && process.env.NODE_ENV !== 'production') { + const axe = await import('@axe-core/react'); + const ReactDOM = await import('react-dom'); + axe.default(App, ReactDOM, 1000, config); + } +}; diff --git a/yarn.lock b/yarn.lock index 175427c4800..7d80d0e1596 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7,6 +7,22 @@ resolved "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz" integrity sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA== +"@actions/core@^1.10.1": + version "1.10.1" + resolved "https://registry.yarnpkg.com/@actions/core/-/core-1.10.1.tgz#61108e7ac40acae95ee36da074fa5850ca4ced8a" + integrity sha512-3lBR9EDAY+iYIpTnTIXmWcNbX3T2kCkAEQGIQx4NVQ0575nk2k3GRZDTPQG+vVtS2izSLmINlxXf0uLtnrTP+g== + dependencies: + "@actions/http-client" "^2.0.1" + uuid "^8.3.2" + +"@actions/http-client@^2.0.1": + version "2.2.1" + resolved "https://registry.yarnpkg.com/@actions/http-client/-/http-client-2.2.1.tgz#ed3fe7a5a6d317ac1d39886b0bb999ded229bb38" + integrity sha512-KhC/cZsq7f8I4LfZSJKgCvEwfkE8o1538VoBeoGzokVLLnbFDEAdFD3UhoMklxo2un9NJVBdANOresx7vTHlHw== + dependencies: + tunnel "^0.0.6" + undici "^5.25.4" + "@adobe/css-tools@4.3.2", "@adobe/css-tools@^4.3.2": version "4.3.2" resolved "https://registry.yarnpkg.com/@adobe/css-tools/-/css-tools-4.3.2.tgz#a6abc715fb6884851fca9dad37fc34739a04fd11" @@ -903,6 +919,21 @@ dependencies: tslib "^2.3.1" +"@axe-core/puppeteer@^4.9.0": + version "4.9.0" + resolved "https://registry.yarnpkg.com/@axe-core/puppeteer/-/puppeteer-4.9.0.tgz#13db7765e86e362f48c9958132f92ec9c24679b2" + integrity sha512-hSlFjfJ6SzrE/XLJllNYbnz5ZgD6MI5+WsNXFfBf1c1I/Zq0jsl147RvHtgMCkgZMIltV5LDupnqj5Uyt8i6Lw== + dependencies: + axe-core "~4.9.0" + +"@axe-core/react@^4.9.0": + version "4.9.0" + resolved "https://registry.yarnpkg.com/@axe-core/react/-/react-4.9.0.tgz#51197c20a9ef72ebef0dbf3c35a1e49c0f01c00d" + integrity sha512-xtqnkFcdxT/T6JD9/hc5Wzv15+m0Qj6VaQCebeIBEveZBOY9nfD6/JIuYuCgWLrgjX+TFgb74nni8XMJvAhVMA== + dependencies: + axe-core "~4.9.0" + requestidlecallback "^0.3.0" + "@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.23.5": version "7.23.5" resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz" @@ -1654,6 +1685,11 @@ resolved "https://registry.npmjs.org/@eslint/js/-/js-8.54.0.tgz" integrity sha512-ut5V+D+fOoWPgGGNj83GGjnntO39xDy6DWxO0wb7Jp3DcMX0TfIqdzHF85VTQkerdyGmuuMD9AKAo5KiNlf/AQ== +"@fastify/busboy@^2.0.0": + version "2.1.1" + resolved "https://registry.yarnpkg.com/@fastify/busboy/-/busboy-2.1.1.tgz#b9da6a878a371829a0502c9b6c1c143ef6663f4d" + integrity sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA== + "@floating-ui/core@^0.7.3": version "0.7.3" resolved "https://registry.npmjs.org/@floating-ui/core/-/core-0.7.3.tgz" @@ -3719,6 +3755,11 @@ axe-core@=4.7.0: resolved "https://registry.npmjs.org/axe-core/-/axe-core-4.7.0.tgz" integrity sha512-M0JtH+hlOL5pLQwHOLNYZaXuhqmvS8oExsqB1SBYgA4Dk7u/xx+YdGHXaK5pyUfed5mYXdlYiphWq3G8cRi5JQ== +axe-core@~4.9.0: + version "4.9.1" + resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.9.1.tgz#fcd0f4496dad09e0c899b44f6c4bb7848da912ae" + integrity sha512-QbUdXJVTpvUTHU7871ppZkdOLBeGUKBQWHkHrvN2V9IQWGMt61zf3B45BtzjxEJzYuj0JBjBZP/hmYS/R9pmAw== + axios@^1.3.4: version "1.6.7" resolved "https://registry.npmjs.org/axios/-/axios-1.6.7.tgz" @@ -9733,6 +9774,11 @@ repeat-string@^1.6.1: resolved "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz" integrity sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w== +requestidlecallback@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/requestidlecallback/-/requestidlecallback-0.3.0.tgz#6fb74e0733f90df3faa4838f9f6a2a5f9b742ac5" + integrity sha512-TWHFkT7S9p7IxLC5A1hYmAYQx2Eb9w1skrXmQ+dS1URyvR8tenMLl4lHbqEOUnpEYxNKpkVMXUgknVpBZWXXfQ== + require-directory@^2.1.1: version "2.1.1" resolved "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz" @@ -10746,6 +10792,11 @@ tunnel-agent@^0.6.0: dependencies: safe-buffer "^5.0.1" +tunnel@^0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/tunnel/-/tunnel-0.0.6.tgz#72f1314b34a5b192db012324df2cc587ca47f92c" + integrity sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg== + type-check@^0.4.0, type-check@~0.4.0: version "0.4.0" resolved "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz" @@ -10876,6 +10927,13 @@ unbzip2-stream@1.4.3: buffer "^5.2.1" through "^2.3.8" +undici@^5.25.4: + version "5.28.4" + resolved "https://registry.yarnpkg.com/undici/-/undici-5.28.4.tgz#6b280408edb6a1a604a9b20340f45b422e373068" + integrity sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g== + dependencies: + "@fastify/busboy" "^2.0.0" + unified@^10.0.0: version "10.1.2" resolved "https://registry.npmjs.org/unified/-/unified-10.1.2.tgz" From 5a9abed246f0488133f563bcadf1d7aed64148f9 Mon Sep 17 00:00:00 2001 From: Heather Date: Tue, 7 May 2024 17:05:56 -0400 Subject: [PATCH 2/8] pass build dir --- .github/workflows/accessibility_test.yml | 2 +- .github/workflows/scripts/check_for_changed_pages.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/accessibility_test.yml b/.github/workflows/accessibility_test.yml index f05edcd9cda..0c72ed31548 100644 --- a/.github/workflows/accessibility_test.yml +++ b/.github/workflows/accessibility_test.yml @@ -29,7 +29,7 @@ jobs: github-token: ${{ secrets.GITHUB_TOKEN }} script: | const { getChangedPages } = require('./.github/workflows/scripts/check_for_changed_pages.js'); - return getChangedPages({github, context}); + return getChangedPages({github, context}, ${{ env.BUILD_DIR }}); - name: Run site run: | python -m http.server 3000 -d ${{ env.BUILD_DIR }} & diff --git a/.github/workflows/scripts/check_for_changed_pages.js b/.github/workflows/scripts/check_for_changed_pages.js index 0533a13ac50..1c4c413cda6 100644 --- a/.github/workflows/scripts/check_for_changed_pages.js +++ b/.github/workflows/scripts/check_for_changed_pages.js @@ -1,5 +1,5 @@ module.exports = { - getChangedPages: ({ github, context }) => { + getChangedPages: ({ github, context }, buildDir) => { const fs = require('fs'); const cheerio = require('cheerio'); @@ -45,7 +45,7 @@ module.exports = { } }); - const siteMap = fs.readFileSync('public/sitemap.xml'); + const siteMap = fs.readFileSync(`${buildDir}/sitemap.xml`); const siteMapParse = cheerio.load(siteMap, { xml: true From 8c1583db415568cd3b283401fe2605512c1fffea Mon Sep 17 00:00:00 2001 From: Heather Date: Tue, 7 May 2024 17:16:54 -0400 Subject: [PATCH 3/8] update --- .github/workflows/scripts/check_for_changed_pages.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/scripts/check_for_changed_pages.js b/.github/workflows/scripts/check_for_changed_pages.js index 1c4c413cda6..01bdd1ee165 100644 --- a/.github/workflows/scripts/check_for_changed_pages.js +++ b/.github/workflows/scripts/check_for_changed_pages.js @@ -4,6 +4,8 @@ module.exports = { const cheerio = require('cheerio'); const urlList = []; + + console.log('buildDir: ', buildDir); const { issue: { number: issue_number }, From f91ce8aacf6f62bdac7cda129b09054600f50f80 Mon Sep 17 00:00:00 2001 From: Heather Date: Wed, 8 May 2024 08:41:37 -0400 Subject: [PATCH 4/8] updates --- .github/workflows/accessibility_test.yml | 2 +- .github/workflows/scripts/check_for_changed_pages.js | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/.github/workflows/accessibility_test.yml b/.github/workflows/accessibility_test.yml index 0c72ed31548..f05edcd9cda 100644 --- a/.github/workflows/accessibility_test.yml +++ b/.github/workflows/accessibility_test.yml @@ -29,7 +29,7 @@ jobs: github-token: ${{ secrets.GITHUB_TOKEN }} script: | const { getChangedPages } = require('./.github/workflows/scripts/check_for_changed_pages.js'); - return getChangedPages({github, context}, ${{ env.BUILD_DIR }}); + return getChangedPages({github, context}); - name: Run site run: | python -m http.server 3000 -d ${{ env.BUILD_DIR }} & diff --git a/.github/workflows/scripts/check_for_changed_pages.js b/.github/workflows/scripts/check_for_changed_pages.js index 01bdd1ee165..8a2a224ebbe 100644 --- a/.github/workflows/scripts/check_for_changed_pages.js +++ b/.github/workflows/scripts/check_for_changed_pages.js @@ -1,11 +1,9 @@ module.exports = { - getChangedPages: ({ github, context }, buildDir) => { + getChangedPages: ({ github, context }) => { const fs = require('fs'); const cheerio = require('cheerio'); const urlList = []; - - console.log('buildDir: ', buildDir); const { issue: { number: issue_number }, @@ -47,7 +45,7 @@ module.exports = { } }); - const siteMap = fs.readFileSync(`${buildDir}/sitemap.xml`); + const siteMap = fs.readFileSync('client/www/next-build/sitemap.xml'); const siteMapParse = cheerio.load(siteMap, { xml: true From b21c69f6712038cae854a19a373eb26e0ad150fe Mon Sep 17 00:00:00 2001 From: Heather Date: Wed, 8 May 2024 09:11:51 -0400 Subject: [PATCH 5/8] add test violation --- src/pages/[platform]/build-a-backend/index.mdx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/pages/[platform]/build-a-backend/index.mdx b/src/pages/[platform]/build-a-backend/index.mdx index 74a0e1ac4a2..21c830ad6eb 100644 --- a/src/pages/[platform]/build-a-backend/index.mdx +++ b/src/pages/[platform]/build-a-backend/index.mdx @@ -1,5 +1,6 @@ import { getChildPageNodes } from '@/utils/getChildPageNodes'; import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; +import { Text } from '@aws-amplify/ui-react'; export const meta = { title: 'Build & connect backend', @@ -32,4 +33,6 @@ export function getStaticProps(context) { }; } +Test text color + From 9b09d6eb212d6d785dd09e50806fc97466b07662 Mon Sep 17 00:00:00 2001 From: Heather Date: Wed, 8 May 2024 10:22:47 -0400 Subject: [PATCH 6/8] undo page change --- src/pages/[platform]/build-a-backend/index.mdx | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/pages/[platform]/build-a-backend/index.mdx b/src/pages/[platform]/build-a-backend/index.mdx index 21c830ad6eb..74a0e1ac4a2 100644 --- a/src/pages/[platform]/build-a-backend/index.mdx +++ b/src/pages/[platform]/build-a-backend/index.mdx @@ -1,6 +1,5 @@ import { getChildPageNodes } from '@/utils/getChildPageNodes'; import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; -import { Text } from '@aws-amplify/ui-react'; export const meta = { title: 'Build & connect backend', @@ -33,6 +32,4 @@ export function getStaticProps(context) { }; } -Test text color - From f6dd6f8dac2c20afd97d2fe23dc71e8e52048ab7 Mon Sep 17 00:00:00 2001 From: Heather Date: Tue, 28 May 2024 12:46:25 -0400 Subject: [PATCH 7/8] test variable for buildDir --- .github/workflows/accessibility_test.yml | 2 +- .github/workflows/scripts/check_for_changed_pages.js | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/accessibility_test.yml b/.github/workflows/accessibility_test.yml index f05edcd9cda..9e1700c3b2e 100644 --- a/.github/workflows/accessibility_test.yml +++ b/.github/workflows/accessibility_test.yml @@ -29,7 +29,7 @@ jobs: github-token: ${{ secrets.GITHUB_TOKEN }} script: | const { getChangedPages } = require('./.github/workflows/scripts/check_for_changed_pages.js'); - return getChangedPages({github, context}); + return getChangedPages({github, context}, env.BUILD_DIR); - name: Run site run: | python -m http.server 3000 -d ${{ env.BUILD_DIR }} & diff --git a/.github/workflows/scripts/check_for_changed_pages.js b/.github/workflows/scripts/check_for_changed_pages.js index 8a2a224ebbe..10a9391ba55 100644 --- a/.github/workflows/scripts/check_for_changed_pages.js +++ b/.github/workflows/scripts/check_for_changed_pages.js @@ -1,5 +1,6 @@ module.exports = { - getChangedPages: ({ github, context }) => { + getChangedPages: ({ github, context }, buildDir) => { + console.log('buildDir: ', buildDir); const fs = require('fs'); const cheerio = require('cheerio'); @@ -45,7 +46,7 @@ module.exports = { } }); - const siteMap = fs.readFileSync('client/www/next-build/sitemap.xml'); + const siteMap = fs.readFileSync(`${buildDir}/sitemap.xml`); const siteMapParse = cheerio.load(siteMap, { xml: true From 81cd14aad4ae9242657343603957c94f80ac24c6 Mon Sep 17 00:00:00 2001 From: Heather Date: Thu, 6 Jun 2024 12:58:28 -0400 Subject: [PATCH 8/8] test img --- src/pages/[platform]/build-a-backend/index.mdx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/pages/[platform]/build-a-backend/index.mdx b/src/pages/[platform]/build-a-backend/index.mdx index 74a0e1ac4a2..b01510c7727 100644 --- a/src/pages/[platform]/build-a-backend/index.mdx +++ b/src/pages/[platform]/build-a-backend/index.mdx @@ -33,3 +33,5 @@ export function getStaticProps(context) { } + +![](/images/cli/user-creation/access-keys-done.png)