diff --git a/.github/workflows/deploy-gh.yml b/.github/workflows/deploy-gh.yml index c2c5148..25c35b1 100644 --- a/.github/workflows/deploy-gh.yml +++ b/.github/workflows/deploy-gh.yml @@ -26,27 +26,30 @@ jobs: uses: actions/checkout@v4 - name: Use Node.js - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version-file: '.nvmrc' - name: Cache node_modules - uses: actions/cache@v3 + uses: actions/cache@v4 id: cache-node-modules with: path: node_modules - key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package.json') }} + key: ${{ runner.os }}-build-${{ hashFiles('**/package.json') }} - name: Cache dist - uses: actions/cache@v3 + uses: actions/cache@v4 id: cache-dist with: path: packages/client/dist - key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ github.sha }} + key: ${{ runner.os }}-build-${{ github.sha }} - name: Install run: npm install + - name: Setup SPA on Github Pages + run: node packages/client/tasks/setup-gh-pages.mjs + - name: Build run: npm run all:build @@ -59,14 +62,11 @@ jobs: uses: actions/checkout@v4 - name: Restore dist cache - uses: actions/cache@v3 + uses: actions/cache@v4 id: cache-dist with: path: packages/client/dist - key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ github.sha }} - - - name: Copy index as 400 file for github pages - run: cp packages/client/dist/index.html packages/client/dist/400.html + key: ${{ runner.os }}-build-${{ github.sha }} - name: Deploy 🚀 uses: JamesIves/github-pages-deploy-action@v4 diff --git a/packages/client/.env b/packages/client/.env index 3dc31a9..d221d15 100644 --- a/packages/client/.env +++ b/packages/client/.env @@ -8,4 +8,7 @@ REACT_APP_STAC_API= ## Theming # REACT_APP_THEME_PRIMARY_COLOR='#6A5ACD' -# REACT_APP_THEME_SECONDARY_COLOR='#048A81' \ No newline at end of file +# REACT_APP_THEME_SECONDARY_COLOR='#048A81' + +## Don't set the public url here. Check the README.md file for more information +# PUBLIC_URL= Do not set here \ No newline at end of file diff --git a/packages/client/README.md b/packages/client/README.md index 320b73b..5b28d1d 100644 --- a/packages/client/README.md +++ b/packages/client/README.md @@ -14,6 +14,7 @@ Some client options are controlled by environment variables. These are: ## Title and description of the app for metadata APP_TITLE APP_DESCRIPTION +PUBLIC_URL # API ## If the app is being served in from a subfolder, the domain url must be set. @@ -26,6 +27,15 @@ REACT_APP_THEME_PRIMARY_COLOR REACT_APP_THEME_SECONDARY_COLOR ``` +**Public URL** +It is recommended to always set the `PUBLIC_URL` environment variable on a production build. +If the app is being served from a subfolder, the `PUBLIC_URL` should include the subfolder path. **Do not include a trailing slash.** + +For example, if the app is being served from `https://example.com/stac-manager`, the `PUBLIC_URL` should be set to `https://example.com/stac-manager`. + +> [!IMPORTANT] +> The `PUBLIC_URL` environment variable must be set before running the build script, and therefore the `.env` file cannot be used to set this variable. + You must provide a value for the `REACT_APP_STAC_API` environment variable. This should be the URL of the STAC API you wish to interact with. If the `REACT_APP_STAC_BROWSER` environment variable is not set, [Radiant Earth's STAC Browser](https://radiantearth.github.io/stac-browser/) will be used by default, which will connect to the STAC API specified in `REACT_APP_STAC_API`. diff --git a/packages/client/posthtml.config.js b/packages/client/posthtml.config.js index e4ff1d4..c8c18dc 100644 --- a/packages/client/posthtml.config.js +++ b/packages/client/posthtml.config.js @@ -28,7 +28,7 @@ module.exports = { locals: { appTitle: process.env.APP_TITLE, appDescription: process.env.APP_DESCRIPTION, - baseurl: process.env.PUBLIC_URL || '' + baseurl: process.env.PUBLIC_URL || '/' } } } diff --git a/packages/client/src/App.tsx b/packages/client/src/App.tsx index 48b6dbd..0096e6f 100644 --- a/packages/client/src/App.tsx +++ b/packages/client/src/App.tsx @@ -25,11 +25,21 @@ import CollectionDetail from './pages/CollectionDetail'; import Sandbox from './pages/Sandbox'; import { config } from './plugin-system/config'; +let basename: string | undefined; +if (process.env.PUBLIC_URL) { + try { + basename = new URL(process.env.PUBLIC_URL).pathname; + // eslint-disable-next-line @typescript-eslint/no-unused-vars + } catch (error) { + // no-op + } +} + export const App = () => ( - + ( > ; } diff --git a/packages/client/tasks/build.mjs b/packages/client/tasks/build.mjs index 76fcf1f..fcea7ad 100644 --- a/packages/client/tasks/build.mjs +++ b/packages/client/tasks/build.mjs @@ -34,7 +34,15 @@ async function copyFiles() { log.info('📦 Copied static files to dist.'); } -async function parcelServe() { +async function parcelBuild() { + const publicUrl = process.env.PUBLIC_URL || '/'; + + if (publicUrl && publicUrl !== '/') { + log.warn(`🌍 Building using public URL: ${publicUrl}`); + } else { + log.warn(`🌍 Building without public URL`); + } + const bundler = new Parcel({ entries: `${__dirname}/../src/index.html`, defaultConfig: `${__dirname}/../.parcelrc`, @@ -42,7 +50,7 @@ async function parcelServe() { mode: 'production', defaultTargetOptions: { distDir: `${__dirname}/../dist`, - publicUrl: process.env.PUBLIC_URL || '/' + publicUrl }, additionalReporters: [ { @@ -58,8 +66,9 @@ async function parcelServe() { log.info(`✨ Built ${bundles.length} bundles in ${buildTime}ms!`); } catch (err) { log.warn(err.diagnostics); + process.exit(1); } } copyFiles(); -parcelServe(); +parcelBuild(); diff --git a/packages/client/tasks/setup-gh-pages.mjs b/packages/client/tasks/setup-gh-pages.mjs new file mode 100644 index 0000000..cf4cfa3 --- /dev/null +++ b/packages/client/tasks/setup-gh-pages.mjs @@ -0,0 +1,121 @@ +/* global process */ +import path from 'path'; +import { fileURLToPath } from 'url'; +import fs from 'fs-extra'; +import log from 'fancy-log'; + +// Adapted into a script from: https://github.com/rafgraph/spa-github-pages/tree/gh-pages + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +const baseUrl = process.env.PUBLIC_URL || ''; + +const pathIndex = path.join(__dirname, '../src/index.html'); +const path404 = path.join(__dirname, '../static/404.html'); + +async function main() { + log.info('📦 Setting up single page apps on GitHub Pages.'); + + const has404 = await fs.pathExists(path404); + + if (has404) { + log.warn('📦 Found custom 404.html. Skipping setup.'); + process.exit(0); + } + + if (!baseUrl) { + log.warn( + '📦 Public URL not set. Assuming the app is deployed to the root.' + ); + } + + let segments = 0; + if (baseUrl) { + try { + segments = new URL(baseUrl).pathname.split('/').length - 1; + // eslint-disable-next-line @typescript-eslint/no-unused-vars + } catch (error) { + // no-op + } + log.info(`📦 Using ${baseUrl} with ${segments} path segments.`); + } + + const templateScript = ` + + `; + + // Write to index head. + const index = await fs.readFile(pathIndex, 'utf8'); + const newIndex = index.replace('', `\n${templateScript}`); + await fs.writeFile(pathIndex, newIndex); + + const template404 = ` + + + + Single Page Apps for GitHub Pages + + + + +`; + + // Write to 404.html. + await fs.writeFile(path404, template404); + + log.info('✅ GitHub Pages setup complete.'); +} + +main();