Repository navigation
ci: deploy the web app to GitHub Pages #57
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
d8ce4c1
test(graph-web): serve the app under a sub-path base in the end-to-en…
Mearman e416ea1
ci: deploy the web app to GitHub Pages on push to main
Mearman 565f8b7
docs(graph-web): document the Pages deployment and how its base is set
Mearman 0399e19
feat(graph-web): keep nothing on a shared github.io origin
Mearman 9fb763b
docs(graph-web): state that the deployed copy is an ephemeral demo
Mearman de030b6
revert(graph-web): remove the ephemeral mode for shared github.io ori…
Mearman File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| name: Pages | ||
|
|
||
| # Deploys the web app (packages/targets/graph-web) to GitHub Pages as a project site, https://exadev.github.io/process-graph/. The repository's Pages source must be set to "GitHub Actions" once (`gh api repos/ExaDev/process-graph/pages -X POST -f build_type=workflow`); until it is, the deploy job fails at the deployment step. | ||
|
|
||
| on: | ||
| push: | ||
| branches: [main] | ||
|
|
||
| permissions: | ||
| contents: read | ||
|
|
||
| jobs: | ||
| build: | ||
| name: Build | ||
| runs-on: ubuntu-latest | ||
| timeout-minutes: 15 | ||
| steps: | ||
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | ||
| with: | ||
| persist-credentials: false | ||
| # The app's base path is the repository name, so the build serves under /process-graph/ and its manifest scope, service worker scope and asset URLs all follow it. GRAPH_WEB_BASE is read by vite.config.ts and declared in the _build task's env so turbo passes it through and keys its cache on it. | ||
| - uses: ./.github/actions/setup-workspace | ||
| with: | ||
| task: pages | ||
| command: GRAPH_WEB_BASE="/${GITHUB_REPOSITORY#*/}/" pnpm exec turbo run _build --filter=@exadev/process-graph-web | ||
| - uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0 | ||
| with: | ||
| path: packages/targets/graph-web/dist | ||
|
|
||
| deploy: | ||
| name: Deploy | ||
| needs: build | ||
| runs-on: ubuntu-latest | ||
| timeout-minutes: 10 | ||
| # Two deploys never overlap and a running one is never cancelled: a cancelled deployment can leave the site half-published, so the next run queues behind it. | ||
| concurrency: | ||
| group: pages | ||
| cancel-in-progress: false | ||
| permissions: | ||
| pages: write # to publish the artifact | ||
| id-token: write # OIDC proof to the Pages service that the deploy comes from this workflow | ||
| environment: | ||
| name: github-pages | ||
| url: ${{ steps.deployment.outputs.page_url }} | ||
| steps: | ||
| - id: deployment | ||
| uses: actions/deploy-pages@368f82528645a54fb793d4d04e342629a3f51346 # v5.0.1 | ||
|
Mearman marked this conversation as resolved.
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| import { build, preview } from "vite"; | ||
| import { basePath, basePathOutDir, basePathPreviewPort } from "./base-path.ts"; | ||
|
|
||
| const configFile = new URL("../vite.config.ts", import.meta.url).pathname; | ||
|
|
||
| /** | ||
| * Playwright's global setup: builds the app a second time with `GRAPH_WEB_BASE` set to the sub-path, into its own output directory, and previews it on its own port for the base-path end-to-end test. This is not a `webServer` entry because Playwright starts those before global setup, when the build does not exist yet. The variable is set on this process before Vite loads `vite.config.ts`, which reads it once. | ||
| * @returns The teardown, which stops the preview server. | ||
| */ | ||
| export default async function serveAtBasePath(): Promise<() => Promise<void>> { | ||
| process.env.GRAPH_WEB_BASE = basePath; | ||
| await build({ | ||
| configFile, | ||
| logLevel: "warn", | ||
| build: { outDir: basePathOutDir, emptyOutDir: true }, | ||
| }); | ||
| const server = await preview({ | ||
| configFile, | ||
| logLevel: "warn", | ||
| build: { outDir: basePathOutDir }, | ||
| preview: { | ||
| host: "127.0.0.1", | ||
| port: basePathPreviewPort, | ||
| strictPort: true, | ||
| }, | ||
| }); | ||
|
|
||
| return async () => { | ||
| await server.close(); | ||
| }; | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| import { expect, test } from "@playwright/test"; | ||
| import { basePath } from "./base-path.ts"; | ||
|
|
||
| // Runs against the build made with `GRAPH_WEB_BASE` set to a sub-path (see base-path-build.ts), served as a project site serves it, under the sub-path. | ||
|
|
||
| test("serves the shell, manifest and service worker under the base path and requests nothing outside it", async ({ | ||
| page, | ||
| baseURL, | ||
| }) => { | ||
| const origin = new URL(baseURL ?? "").origin; | ||
| const paths: string[] = []; | ||
| page.on("request", (request) => { | ||
| const url = new URL(request.url()); | ||
| if (url.origin === origin) { | ||
| paths.push(url.pathname); | ||
| } | ||
| }); | ||
|
|
||
| await page.goto(basePath); | ||
| await expect(page.getByLabel("Token", { exact: true })).toBeVisible(); | ||
| const scope = await page.evaluate(async () => { | ||
| const registration = await navigator.serviceWorker.ready; | ||
|
|
||
| return registration.scope; | ||
| }); | ||
| expect(scope).toBe(`${origin}${basePath}`); | ||
|
|
||
| const manifestLink = await page | ||
| .locator('link[rel="manifest"]') | ||
| .getAttribute("href"); | ||
| expect(manifestLink).toBe(`${basePath}manifest.webmanifest`); | ||
| const manifest: unknown = await ( | ||
| await page.request.get(`${basePath}manifest.webmanifest`) | ||
| ).json(); | ||
| expect(manifest).toMatchObject({ start_url: basePath, scope: basePath }); | ||
| const icon = await page.request.get(`${basePath}icons/maskable-512.png`); | ||
| expect(icon.headers()["content-type"]).toBe("image/png"); | ||
|
|
||
| expect(paths).not.toHaveLength(0); | ||
| expect(paths.filter((path) => !path.startsWith(basePath))).toEqual([]); | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| /** The sub-path a project site serves the app under (`https://<owner>.github.io/process-graph/`), which the base-path build and its tests use. */ | ||
| export const basePath = "/process-graph/"; | ||
|
|
||
| /** Where the base-path build is written, beside `dist/` so it never disturbs the root build the other end-to-end tests serve. */ | ||
| export const basePathOutDir = "dist-base-path"; | ||
|
|
||
| /** The port the base-path build is previewed on, pinned with `--strictPort` like the root build's. */ | ||
| export const basePathPreviewPort = 4174; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,61 @@ | ||
| import { | ||
| expect, | ||
| test as base, | ||
| type Locator, | ||
| type Page, | ||
| } from "@playwright/test"; | ||
| import { startGitHubFixture, type GitHubFixture } from "./github-fixture.ts"; | ||
|
|
||
| export const githubApiHost = "api.github.com"; | ||
|
|
||
| // What the end-to-end tests that run the app against the GitHub fixture share. | ||
|
|
||
| /** Every test gets its own fixture server, and its browser context's requests to GitHub's API are answered by it. Playwright forwards the request from the test process, so the browser's own request still goes to api.github.com. */ | ||
| export const test = base.extend<{ readonly github: GitHubFixture }>({ | ||
| github: [ | ||
| async ({ context }, use) => { | ||
| const fixture = await startGitHubFixture(); | ||
| await context.route(`https://${githubApiHost}/**`, async (route) => { | ||
| const response = await route.fetch({ url: fixture.url }); | ||
| await route.fulfill({ response }); | ||
| }); | ||
| await use(fixture); | ||
| await fixture.close(); | ||
| }, | ||
| { auto: true }, | ||
| ], | ||
| }); | ||
|
|
||
| /** An element's card on the canvas. */ | ||
| export const card = (page: Page, id: string): Locator => | ||
| page.locator(`.react-flow__node [data-element-id="${id}"]`); | ||
|
|
||
| /** Saves a token, adds the scopes and starts their sync. */ | ||
| export const syncScopes = async ( | ||
| page: Page, | ||
| token: string, | ||
| scopes: readonly string[], | ||
| ): Promise<void> => { | ||
| await page.getByLabel("Token", { exact: true }).fill(token); | ||
| await page.getByRole("button", { name: "Save on this device" }).click(); | ||
| // Each scope is added after the one before, as a person would add them. | ||
| await scopes.reduce(async (previous, scope) => { | ||
| await previous; | ||
| await page | ||
| .getByLabel("Add an organisation, user or repository") | ||
| .fill(scope); | ||
| await page.getByRole("button", { name: "Add", exact: true }).click(); | ||
| }, Promise.resolve()); | ||
| await page.getByRole("button", { name: "Sync", exact: true }).click(); | ||
| }; | ||
|
|
||
| export const syncFinished = async (page: Page): Promise<void> => { | ||
| await expect(page.locator("[data-progress]")).toContainText("Sync finished"); | ||
| }; | ||
|
|
||
| /** Waits until a service worker is active for the page's scope, so a reload is served from its precache. */ | ||
| export const serviceWorkerActive = async (page: Page): Promise<void> => { | ||
| await page.evaluate(async () => { | ||
| await navigator.serviceWorker.ready; | ||
| }); | ||
| }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.