diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d398360bf39..534db3dd80a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -35,6 +35,42 @@ npm install npm start ``` +#### GitHub Token + +The documentation build requires a GitHub Personal Access Token to fetch Ionic Framework release notes. The build will still work locally without it (release notes will be empty), but it's required for Vercel preview and production builds. + +**Local Development:** + +1. Create a [fine-grained Personal Access Token](https://github.com/settings/personal-access-tokens/new) with: + + - **Repository access**: Public repositories (read-only) + - **Expiration**: 366 days (update annually) + +2. Add the token to your shell profile (`~/.zshrc`, `~/.bashrc`, etc.): + + ```sh + export GITHUB_TOKEN=github_pat_... + ``` + +3. Reload your shell or run `source ~/.zshrc` (or equivalent) + +**Vercel:** + +1. Create a [fine-grained Personal Access Token](https://github.com/settings/personal-access-tokens/new) with the same settings as above, but with: + + - **Owner**: ionic-team organization (not your personal account) + +2. Add the token to Vercel project settings: + + - Go to your project on Vercel + - Navigate to **Settings → Environment Variables** + - Add `GITHUB_TOKEN` with the token value + - Select Production and Preview environments + +3. Redeploy the project for the token to take effect + +Without the token, the build will fail with an error message indicating the token is missing. + ### Linting Documentation This repository uses [Prettier](https://prettier.io/), an opinionated code formatter, in order to keep consistent formatting throughout the documentation. Run the following command to automatically fix all formatting, and then push any changes: diff --git a/package-lock.json b/package-lock.json index 954dabe144d..5686eef03bb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,7 +19,6 @@ "@stackblitz/sdk": "^1.6.0", "@tippyjs/react": "^4.2.6", "clsx": "^1.1.1", - "concurrently": "^6.2.0", "crowdin": "^3.5.0", "docusaurus-plugin-copy-page-button": "^0.8.2", "docusaurus-plugin-module-alias": "^0.0.2", @@ -6583,50 +6582,6 @@ "node": ">=8" } }, - "node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "node_modules/cliui/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" - }, - "node_modules/cliui/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cliui/node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, "node_modules/clone": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", @@ -6813,41 +6768,6 @@ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" }, - "node_modules/concurrently": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-6.5.1.tgz", - "integrity": "sha512-FlSwNpGjWQfRwPLXvJ/OgysbBxPkWpiVjy1042b0U7on7S7qwwMIILRj7WTN1mTgqa582bG6NFuScOoh6Zgdag==", - "dependencies": { - "chalk": "^4.1.0", - "date-fns": "^2.16.1", - "lodash": "^4.17.21", - "rxjs": "^6.6.3", - "spawn-command": "^0.0.2-1", - "supports-color": "^8.1.0", - "tree-kill": "^1.2.2", - "yargs": "^16.2.0" - }, - "bin": { - "concurrently": "bin/concurrently.js" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/concurrently/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, "node_modules/config-chain": { "version": "1.1.13", "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", @@ -7733,21 +7653,6 @@ "resolved": "https://registry.npmjs.org/dag-map/-/dag-map-1.0.2.tgz", "integrity": "sha512-+LSAiGFwQ9dRnRdOeaj7g47ZFJcOUPukAP8J3A3fuZ1g9Y44BG+P1sgApjLXTQPOzC4+7S9Wr8kXsfpINM4jpw==" }, - "node_modules/date-fns": { - "version": "2.30.0", - "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", - "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", - "dependencies": { - "@babel/runtime": "^7.21.0" - }, - "engines": { - "node": ">=0.11" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/date-fns" - } - }, "node_modules/debounce": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz", @@ -9269,14 +9174,6 @@ "node": ">=6.9.0" } }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, "node_modules/get-intrinsic": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", @@ -16851,14 +16748,6 @@ "node": ">=0.10" } }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/require-from-string": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", @@ -17033,22 +16922,6 @@ "queue-microtask": "^1.2.2" } }, - "node_modules/rxjs": { - "version": "6.6.7", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", - "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", - "dependencies": { - "tslib": "^1.9.0" - }, - "engines": { - "npm": ">=2.0.0" - } - }, - "node_modules/rxjs/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -17715,11 +17588,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/spawn-command": { - "version": "0.0.2-1", - "resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2-1.tgz", - "integrity": "sha512-n98l9E2RMSJ9ON1AKisHzz7V42VDiBQGY6PB1BwRglz99wpVsSuGzQ+jOi6lFXBGVTCrRpltvjm+/XA+tpeJrg==" - }, "node_modules/spdy": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", @@ -18199,14 +18067,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/tree-kill": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", - "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", - "bin": { - "tree-kill": "cli.js" - } - }, "node_modules/trim-lines": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", @@ -19367,14 +19227,6 @@ "xml-js": "bin/cli.js" } }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "engines": { - "node": ">=10" - } - }, "node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", @@ -19388,23 +19240,6 @@ "node": ">= 6" } }, - "node_modules/yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/yargs-parser": { "version": "21.1.1", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", @@ -19414,32 +19249,6 @@ "node": ">=12" } }, - "node_modules/yargs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" - }, - "node_modules/yargs/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/yargs/node_modules/yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", - "engines": { - "node": ">=10" - } - }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index aa56108b5f7..3efef989bef 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "node": ">=20.0.0" }, "scripts": { - "prebuild": "npm run generate-markdown; scripts/i18n.sh; if [ -n \"$CROWDIN_PERSONAL_TOKEN\" ]; then npm run crowdin:sync; fi", + "prebuild": "npm run generate-markdown && scripts/i18n.sh && if [ -n \"$CROWDIN_PERSONAL_TOKEN\" ]; then npm run crowdin:sync; fi", "build": "npm run build:${VERCEL_ENV:-preview}", "build:preview": "docusaurus build --locale en", "build:production": "docusaurus build", @@ -14,7 +14,7 @@ "crowdin:sync": "docusaurus write-translations && crowdin upload && crowdin download", "deploy": "docusaurus deploy", "docusaurus": "docusaurus", - "generate-markdown": "node scripts/native.mjs && concurrently \"node scripts/cli.mjs\" \"node scripts/release-notes.mjs\"", + "generate-markdown": "node scripts/native.mjs && node scripts/cli.mjs && node scripts/release-notes.mjs", "lint": "npm run prettier -- --write", "serve": "docusaurus serve", "playground:new": "hygen playground new", @@ -48,7 +48,6 @@ "@stackblitz/sdk": "^1.6.0", "@tippyjs/react": "^4.2.6", "clsx": "^1.1.1", - "concurrently": "^6.2.0", "crowdin": "^3.5.0", "docusaurus-plugin-copy-page-button": "^0.8.2", "docusaurus-plugin-module-alias": "^0.0.2", diff --git a/scripts/cli.mjs b/scripts/cli.mjs index 6fdebc8d971..6ccd3efa232 100644 --- a/scripts/cli.mjs +++ b/scripts/cli.mjs @@ -15,6 +15,7 @@ const commandToKebab = (str) => const { commands } = cliJSON; commands.map(writePage); + console.log(`šŸ“Ÿ CLI Commands Generated`); })(); function writePage(page) { diff --git a/scripts/native.mjs b/scripts/native.mjs index 631f32c0f19..404ceffa197 100644 --- a/scripts/native.mjs +++ b/scripts/native.mjs @@ -1,7 +1,6 @@ import { writeFileSync } from 'fs'; import fetch from 'node-fetch'; -// replace with latest once it's relased const tag = 'latest'; const pluginApis = [ @@ -51,7 +50,7 @@ function createApiPage(pluginId, readme, pkgJson) { /** * Cleanup and transform JSDoc content for compatibility with MDX/Docusaurus: - * + * * - Remove HTML comments (``) which are not valid in MDX and will cause parsing errors. * - Escape `{` characters inside blocks because MDX treats `{}` as JavaScript expressions. Unescaped `{` inside code blocks can cause parsing errors. * - Convert JSDoc-style {@link URL|Text} and {@link URL} to proper Markdown links: @@ -97,7 +96,7 @@ async function getPkgJsonData(pluginId) { async function main() { await Promise.all(pluginApis.map(buildPluginApiDocs)); - console.log(`Plugin API Files Updated šŸŽø`); + console.log(`šŸ”Œ Capacitor Plugins Generated`); } function toTitleCase(str) { diff --git a/scripts/release-notes.mjs b/scripts/release-notes.mjs index a4e82310881..57d7db58e36 100644 --- a/scripts/release-notes.mjs +++ b/scripts/release-notes.mjs @@ -10,64 +10,60 @@ const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); const OUTPUT_PATH = resolve(__dirname, '../src/components/page/reference/ReleaseNotes/release-notes.json'); -// export default { -// title: 'Build Release Notes data', -// task: async () => outputJson(OUTPUT_PATH, await getReleases(), { spaces: 2 }) -// }; - -// Get the GitHub Releases from Ionic +// Get the GitHub Releases from Ionic Framework // ------------------------------------------------------------------------------- -// This requires an environment GITHUB_TOKEN otherwise it may fail -// -// To add a GITHUB_TOKEN, follow the steps to create a personal access token: -// https://docs.github.com/en/enterprise-cloud@latest/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token -// and then authorize it to work with SSO: -// https://docs.github.com/en/enterprise-cloud@latest/authentication/authenticating-with-saml-single-sign-on/authorizing-a-personal-access-token-for-use-with-saml-single-sign-on +// Requires a GITHUB_TOKEN environment variable. Refer to +// https://github.com/ionic-team/ionic-docs/blob/main/CONTRIBUTING.md#github-token +// for setup instructions. const getReleases = async () => { - try { - const request = await fetch(new URL('repos/ionic-team/ionic/releases', 'https://api.github.com'), { - headers: { - Authorization: process.env.GITHUB_TOKEN !== undefined ? `token ${process.env.GITHUB_TOKEN}` : '', - }, - }); - - const releases = await request.json(); - - // Check that the response is an array in case it was - // successful but returned an object - if (Array.isArray(releases)) { - return releases - .filter((release) => { - const releasePattern = /^v(\d+)\.(\d+)\.(\d+)$/; - - // All non-prerelease, non-alpha, non-beta, non-rc release - return releasePattern.test(release.tag_name); - }) - .map((release) => { - const body = renderMarkdown(release.body.replace(/^#.*/, '')).value; - const published_at = parseDate(release.published_at); - const version = release.tag_name.replace('v', ''); - const type = getVersionType(version); - const { name, tag_name } = release; - - return { - body, - name, - published_at, - tag_name, - type, - version, - }; - }) - .sort((a, b) => { - return -compare(a.tag_name, b.tag_name); - }); - } else { - console.error('There was an issue getting releases:', releases); - return []; + if (!process.env.GITHUB_TOKEN) { + throw new Error('GITHUB_TOKEN environment variable is required.'); + } + + const url = new URL('repos/ionic-team/ionic-framework/releases', 'https://api.github.com'); + const headers = { Authorization: `token ${process.env.GITHUB_TOKEN}` }; + const request = await fetch(url, { headers }); + + if (!request.ok) { + const error = await request.json().catch(() => ({})); + let message = `GitHub API returned ${request.status} ${request.statusText}: ${error.message ?? 'Unknown error'}.`; + + if (request.status === 401 && error.message?.includes('Bad credentials')) { + message += ' Check that GITHUB_TOKEN is set and is a valid GitHub Personal Access Token.'; } - } catch (error) { - return []; + + throw new Error(message); + } + + const releases = await request.json(); + + // Check that the response is an array in case it was + // successful but returned an object + if (Array.isArray(releases)) { + return releases + .filter((release) => { + const releasePattern = /^v(\d+)\.(\d+)\.(\d+)$/; + return releasePattern.test(release.tag_name); + }) + .map((release) => { + const body = renderMarkdown((release.body ?? '').replace(/^#.*/, '')).value; + const published_at = parseDate(release.published_at); + const version = release.tag_name.replace('v', ''); + const type = getVersionType(version); + const { name, tag_name } = release; + + return { + body, + name, + published_at, + tag_name, + type, + version, + }; + }) + .sort((a, b) => -compare(a.tag_name, b.tag_name)); + } else { + throw new Error('GitHub API returned an unexpected response format.'); } }; @@ -98,7 +94,21 @@ function getVersionType(version) { async function run() { const { outputJson } = pkg; - outputJson(OUTPUT_PATH, await getReleases(), { spaces: 2 }); + try { + const releases = await getReleases(); + outputJson(OUTPUT_PATH, releases, { spaces: 2 }); + console.log(`šŸš€ Release Notes Generated`); + } catch (error) { + // Only fail the build in CI environments and the preview + // or production Vercel environments. + const shouldFail = process.env.CI || ['production', 'preview'].includes(process.env.VERCEL_ENV); + if (shouldFail) { + console.error(`\nāŒ Release Notes Failed\n ⇢ ${error.message}\n`); + process.exit(1); + } + console.warn(`\nāš ļø Release Notes Failed\n ⇢ ${error.message}`); + outputJson(OUTPUT_PATH, [], { spaces: 2 }); + } } run(); diff --git a/src/components/page/reference/ReleaseNotes/index.tsx b/src/components/page/reference/ReleaseNotes/index.tsx index c1402d63d1c..eb3d3757c7a 100644 --- a/src/components/page/reference/ReleaseNotes/index.tsx +++ b/src/components/page/reference/ReleaseNotes/index.tsx @@ -15,18 +15,14 @@ interface Release { export default function ReleaseNotes(props: { [key: string]: any }) { if (releases.length === 0) { - console.warn(`Could not load release notes data. Make sure that you have a valid GITHUB_TOKEN. - -Create a personal access token by following the below guide: -https://docs.github.com/en/enterprise-cloud@latest/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token - -and then authorize it to work with SSO: -https://docs.github.com/en/enterprise-cloud@latest/authentication/authenticating-with-saml-single-sign-on/authorizing-a-personal-access-token-for-use-with-saml-single-sign-on`); + console.warn( + `Could not load release notes data. Refer to https://github.com/ionic-team/ionic-docs/blob/main/CONTRIBUTING.md#github-token for setup instructions.` + ); return [ -

+

Unable to load Releases. Please see all releases{' '} - + on GitHub . @@ -38,7 +34,7 @@ https://docs.github.com/en/enterprise-cloud@latest/authentication/authenticating

A complete release history for Ionic Framework is available{' '} - + on GitHub . Documentation for recent releases can also be found below. @@ -50,10 +46,13 @@ https://docs.github.com/en/enterprise-cloud@latest/authentication/authenticating

{releases.map((release: Release, index) => ( -
+