Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 35 additions & 1 deletion .github/workflows/www.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,20 @@ on:
permissions:
contents: read

env:
# name of the netlify site, used to predict the url of a preview deploy
NETLIFY_SITE_NAME: effection

jobs:
deploy-preview:
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
timeout-minutes: 15
environment:
name: Preview
url: ${{ steps.netlify.outputs.unique-url }}
# the alias deploy, so that this link shares an origin with the urls
# baked into the build (see SITE_URL below)
url: ${{ steps.netlify.outputs.stable-url }}
permissions:
contents: read
pull-requests: write
Expand All @@ -33,6 +39,23 @@ jobs:
with:
deno-version: v2.9.1

- name: Compute Site URL
id: site
env:
IS_FORK: ${{ github.event.pull_request.head.repo.full_name != github.repository }}
PR_NUMBER: ${{ github.event.pull_request.number }}
run: |
# an alias deploy lands on a predictable url, so a preview can point
# at itself. a fork deploys anonymously to a url that is not known
# until after the deploy, so it points at production instead.
if [[ "$IS_FORK" == "true" ]]; then
URL="https://${NETLIFY_SITE_NAME}.netlify.app"
else
URL="https://pr-${PR_NUMBER}--${NETLIFY_SITE_NAME}.netlify.app"
fi
echo "url=$URL" >> "$GITHUB_OUTPUT"
echo "Preview will be served from $URL" >> "$GITHUB_STEP_SUMMARY"

- name: Serve Website
run: |
deno run -A main.tsx &
Expand All @@ -43,6 +66,9 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
JSR_API: ${{ secrets.JSR_API }}
# url embedded in resources that staticalize does not rewrite, such
# as llms.txt
SITE_URL: ${{ steps.site.outputs.url }}
timeout-minutes: 5
working-directory: ./www

Expand Down Expand Up @@ -90,6 +116,11 @@ jobs:

DEPLOY_ID=$(echo "$DEPLOY_OUTPUT" | jq -er '.deploy_id')
SITE_NAME=$(echo "$DEPLOY_OUTPUT" | jq -er '.site_name')

if [[ "$SITE_NAME" != "$NETLIFY_SITE_NAME" ]]; then
echo "::warning::deployed to '$SITE_NAME' but urls were built for" \
"'$NETLIFY_SITE_NAME'; update NETLIFY_SITE_NAME in this workflow"
fi
UNIQUE_URL="https://${DEPLOY_ID}--${SITE_NAME}.netlify.app"
STABLE_URL="https://pr-${PR_NUMBER}--${SITE_NAME}.netlify.app"
fi
Expand Down Expand Up @@ -139,6 +170,9 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
JSR_API: ${{ secrets.JSR_API }}
# url embedded in resources that staticalize does not rewrite, such
# as llms.txt
SITE_URL: https://frontside.com/effection
timeout-minutes: 5
working-directory: ./www

Expand Down
4 changes: 2 additions & 2 deletions www/deno.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
{
"tasks": {
"dev": "deno run -A @effectionx/watch deno run -A main.tsx",
"dev": "SITE_URL=http://localhost:8000 deno run -A @effectionx/watch deno run -A main.tsx",
"staticalize": "deno run -A jsr:@frontside/staticalize@0.2.2/cli --site http://localhost:8000 --output=built --base=http://localhost:8000",
"test": "deno test --allow-run --allow-write --allow-read"
"test": "deno test --allow-run --allow-write --allow-read --allow-env"
},
"lint": {
"exclude": [
Expand Down
47 changes: 47 additions & 0 deletions www/plugins/current-request.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { describe, it } from "../testing.ts";
import { expect } from "expect";

import { CurrentRequest } from "../context/request.ts";
import { useSiteUrl } from "./current-request.ts";

describe("useSiteUrl", () => {
it("uses the origin of the current request by default", function* () {
yield* CurrentRequest.set(new Request("http://localhost:8000/llms.txt"));
Deno.env.delete("SITE_URL");

let url = yield* useSiteUrl();

expect(url("/x/")).toEqual("http://localhost:8000/x/");
expect(url("/x/websocket")).toEqual("http://localhost:8000/x/websocket");
});

it("uses SITE_URL when it has no path of its own", function* () {
yield* CurrentRequest.set(new Request("http://127.0.0.1:8000/llms.txt"));
Deno.env.set("SITE_URL", "https://pr-42--effection.netlify.app");

try {
let url = yield* useSiteUrl();

expect(url("/x/")).toEqual("https://pr-42--effection.netlify.app/x/");
expect(url("/blog")).toEqual(
"https://pr-42--effection.netlify.app/blog",
);
} finally {
Deno.env.delete("SITE_URL");
}
});

it("uses SITE_URL when it is set, preserving its path", function* () {
yield* CurrentRequest.set(new Request("http://127.0.0.1:8000/llms.txt"));
Deno.env.set("SITE_URL", "https://frontside.com/effection");

try {
let url = yield* useSiteUrl();

expect(url("/x/")).toEqual("https://frontside.com/effection/x/");
expect(url("/blog")).toEqual("https://frontside.com/effection/blog");
} finally {
Deno.env.delete("SITE_URL");
}
});
});
27 changes: 27 additions & 0 deletions www/plugins/current-request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,30 @@ export function* useCanonicalUrl(options: { base: string }): Operation<string> {
url.pathname = `${url.pathname}${req.pathname}`;
return String(url);
}

/**
* Like {@link useAbsoluteUrlFactory}, except that it honors the `SITE_URL`
* of the published site when one is configured.
*
* Absolute urls in HTML are rewritten to the destination site by staticalize
* when the site is built, so they can just use the origin of the request.
* Urls inside non HTML resources such as `llms.txt` are copied verbatim, so
* they need to be published under `SITE_URL` instead of the loopback address
* that the build serves from. With no `SITE_URL`, they point at the dev
* server, same as every other absolute url.
*/
export function* useSiteUrl(): Operation<(path: string) => string> {
let siteUrl = Deno.env.get("SITE_URL");

if (!siteUrl) {
return yield* useAbsoluteUrlFactory();
}

let base = new URL(siteUrl);

return (path) => {
let url = new URL(base);
url.pathname = posixNormalize(`${base.pathname}/${path}`);
return url.toString();
};
}
10 changes: 5 additions & 5 deletions www/routes/blog-feed-route.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { Operation } from "effection";
import { stringify } from "@libs/xml";

import { useBlog } from "../resources/blog.ts";
import { useSiteUrl } from "../plugins/current-request.ts";

/**
* RSS 2.0 feed for the blog
Expand All @@ -11,8 +12,7 @@ export function blogFeedRoute() {
*handler(): Operation<Response> {
let blog = yield* useBlog();
let posts = blog.getPosts();

let baseUrl = "https://frontside.com/effection";
let url = yield* useSiteUrl();

let xml = stringify({
"@version": "1.0",
Expand All @@ -22,18 +22,18 @@ export function blogFeedRoute() {
"@xmlns:atom": "http://www.w3.org/2005/Atom",
channel: {
title: "Effection Blog",
link: `${baseUrl}/blog`,
link: url("/blog"),
description:
"Tutorials, announcements, and insights about structured concurrency in JavaScript with Effection.",
language: "en-us",
lastBuildDate: new Date().toUTCString(),
"atom:link": {
"@href": `${baseUrl}/blog/feed.xml`,
"@href": url("/blog/feed.xml"),
"@rel": "self",
"@type": "application/rss+xml",
},
item: posts.slice(0, 20).map((post) => {
let postUrl = `${baseUrl}/blog/${post.id}/`;
let postUrl = url(`/blog/${post.id}/`);
return {
title: post.title,
link: postUrl,
Expand Down
20 changes: 13 additions & 7 deletions www/routes/llms-txt-route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { Operation } from "effection";
import { all } from "effection";
import { useWorkspaces } from "../lib/workspaces/mod.ts";
import type { SitemapRoute } from "../plugins/sitemap.ts";
import { useSiteUrl } from "../plugins/current-request.ts";
import type { Package } from "../lib/package/types.ts";
import {
groupPackagesByCategory,
Expand All @@ -24,6 +25,7 @@ export function llmsTxtRoute(): SitemapRoute<Response> {
return [{ pathname: generate() }];
},
*handler(): Operation<Response> {
let url = yield* useSiteUrl();
let workspaces = yield* useWorkspaces("thefrontside/effectionx");
let categories = yield* useTaxonomy("thefrontside/effectionx");
let packages = yield* workspaces.getAllPackages();
Expand Down Expand Up @@ -52,7 +54,9 @@ export function llmsTxtRoute(): SitemapRoute<Response> {
(category) => {
let packageLines = category.packages.map((pkg) => {
let shortDesc = truncateToFirstSentence(pkg.description, 120);
return `- [${pkg.name}](https://frontside.com/effection/x/${pkg.workspaceName}): ${shortDesc}`;
return `- [${pkg.name}](${
url(`/x/${pkg.workspaceName}`)
}): ${shortDesc}`;
});

return [
Expand All @@ -73,7 +77,7 @@ export function llmsTxtRoute(): SitemapRoute<Response> {
"",
...categorizedContent,
"",
LLMS_TXT_FOOTER,
llmsTxtFooter(url),
].join("\n");

return new Response(content, {
Expand Down Expand Up @@ -146,16 +150,17 @@ If any other document conflicts with AGENTS.md, **AGENTS.md takes precedence**.
---
`;

const LLMS_TXT_FOOTER = `## Optional
function llmsTxtFooter(url: (path: string) => string): string {
return `## Optional

- [Full EffectionX catalog with documentation](https://frontside.com/effection/x/)
- [Effection Blog](https://frontside.com/effection/blog)
- [Full EffectionX catalog with documentation](${url("/x/")})
- [Effection Blog](${url("/blog")})

---

[AGENTS.md]: https://raw.githubusercontent.com/thefrontside/effection/v4/AGENTS.md
[API]: https://frontside.com/effection/api/
[Guides]: https://frontside.com/effection/guides/v4
[API]: ${url("/api/")}
[Guides]: ${url("/guides/v4")}
[Thinking in Effection]: https://raw.githubusercontent.com/thefrontside/effection/v4/docs/thinking-in-effection.mdx
[Async Rosetta Stone]: https://raw.githubusercontent.com/thefrontside/effection/v4/docs/async-rosetta-stone.mdx
[Operations]: https://raw.githubusercontent.com/thefrontside/effection/v4/docs/operations.mdx
Expand All @@ -164,3 +169,4 @@ const LLMS_TXT_FOOTER = `## Optional
[Spawn]: https://raw.githubusercontent.com/thefrontside/effection/v4/docs/spawn.mdx
[Collections]: https://raw.githubusercontent.com/thefrontside/effection/v4/docs/collections.mdx
`;
}
Loading