Skip to content
Merged
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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
104 changes: 66 additions & 38 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -1,26 +1,10 @@
name: Publish recipes
name: Validate examples and publish recipes

on:
pull_request:
branches: [master]
paths:
- "comments/**"
- "kitchen-sink/**"
- "examples/**"
- "scripts/**"
- "meta.example.json"
- "TEMPLATE.md"
- ".github/workflows/publish.yml"
push:
branches: [master]
paths:
- "comments/**"
- "kitchen-sink/**"
- "examples/**"
- "scripts/**"
- "meta.example.json"
- "TEMPLATE.md"
- ".github/workflows/publish.yml"
workflow_dispatch:
inputs:
only:
Expand All @@ -37,23 +21,65 @@ concurrency:
cancel-in-progress: false

jobs:
apps:
examples:
name: Build ${{ matrix.app }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
app: [comments, kitchen-sink]
app:
- jekyll
- gatsby
- zero
- blog-with-custom-templates
- nextjs-static
- nextjs
- astro-with-wordpress
- functions-php
- kitchen-sink
- migrate-from-cloudflare-pages
- migrate-from-vercel
- migrate-from-netlify
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- name: Install
working-directory: ${{ matrix.app }}
run: bun install
- name: Test
- uses: ruby/setup-ruby@v1
with:
ruby-version: "3.3"
- name: Install Spacefast CLI
run: bun add -g spacefast@0.4.1
- name: Run app tests
working-directory: ${{ matrix.app }}
run: |
if jq -e '.scripts.test' package.json >/dev/null; then
if [ -f package.json ] && jq -e '.scripts.test' package.json >/dev/null; then
bun install --frozen-lockfile || bun install
bun test
fi
- name: Build deployable artifact
env:
NEXT_TELEMETRY_DISABLED: "1"
GATSBY_TELEMETRY_DISABLED: "1"
XDG_CONFIG_HOME: ${{ runner.temp }}/xdg
run: |
artifact="$RUNNER_TEMP/${{ matrix.app }}.tgz"
case "${{ matrix.app }}" in
zero|kitchen-sink)
cd "${{ matrix.app }}"
sf build --json
artifact="$PWD/.spacefast/zero/artifact.json"
;;
migrate-from-netlify)
sf publish migrate-from-netlify --dry-run --allow-unsupported-platform-features --json
cd migrate-from-netlify
npm install
npm run build
sf build dist --prebuilt --output "$artifact" --json
;;
*)
sf build "${{ matrix.app }}" --output "$artifact" --json
;;
esac
test -s "$artifact"

manifest:
runs-on: ubuntu-latest
Expand All @@ -80,7 +106,7 @@ jobs:
id: deployment
uses: actions/deploy-pages@v4

discover:
discover-recipes:
runs-on: ubuntu-latest
outputs:
slugs: ${{ steps.list.outputs.slugs }}
Expand All @@ -94,14 +120,14 @@ jobs:
BEFORE: ${{ github.event.before }}
run: |
if [ -n "$ONLY" ]; then
test -d "examples/$ONLY"
test -d "recipes/$ONLY"
jq -cn --arg slug "$ONLY" '[ $slug ]' | sed 's/^/slugs=/' >> "$GITHUB_OUTPUT"
exit 0
fi

all_slugs() {
find examples -mindepth 1 -maxdepth 1 -type d -print \
| sed 's#^examples/##' | sort | jq -R -s -c 'split("\n") | map(select(length > 0))'
find recipes -mindepth 1 -maxdepth 1 -type d -print \
| sed 's#^recipes/##' | sort | jq -R -s -c 'split("\n") | map(select(length > 0))'
}

if [ "${{ github.event_name }}" = "pull_request" ] \
Expand All @@ -112,27 +138,29 @@ jobs:
exit 0
fi

changed=$(git diff --name-only "$BEFORE" "${{ github.sha }}")
slugs=$(printf '%s\n' "$changed" | sed -n 's#^examples/\([^/]*\)/site/.*#\1#p' \
# A pure rename (R100) moves a file without changing what it serves.
changed=$(git diff --name-status -M "$BEFORE" "${{ github.sha }}" \
| awk -F'\t' '$1 != "R100" {print $NF}')
slugs=$(printf '%s\n' "$changed" | sed -n 's#^recipes/\([^/]*\)/site/.*#\1#p' \
| sort -u | jq -R -s -c 'split("\n") | map(select(length > 0))')
echo "slugs=$slugs" >> "$GITHUB_OUTPUT"

publish:
needs: discover
if: needs.discover.outputs.slugs != '[]'
publish-recipes:
needs: discover-recipes
if: needs.discover-recipes.outputs.slugs != '[]'
runs-on: ubuntu-latest
strategy:
fail-fast: false
max-parallel: 4
matrix:
slug: ${{ fromJson(needs.discover.outputs.slugs) }}
slug: ${{ fromJson(needs.discover-recipes.outputs.slugs) }}
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- name: Install Spacefast CLI
run: bun add -g spacefast
run: bun add -g spacefast@0.4.1
- name: Build
working-directory: examples/${{ matrix.slug }}
working-directory: recipes/${{ matrix.slug }}
run: |
runtime=$(jq -r '.runtime // "static"' meta.json)
if [ "$runtime" = "zero" ]; then
Expand All @@ -147,7 +175,7 @@ jobs:
fi
- name: Resolve and validate publish directory
id: dir
working-directory: examples/${{ matrix.slug }}
working-directory: recipes/${{ matrix.slug }}
run: |
runtime=$(jq -r '.runtime // "static"' meta.json)
if [ "$runtime" = "zero" ]; then
Expand All @@ -162,7 +190,7 @@ jobs:
fi
- name: Publish Spacefast space
if: github.event_name != 'pull_request'
working-directory: examples/${{ matrix.slug }}
working-directory: recipes/${{ matrix.slug }}
env:
SPACEFAST_TOKEN: ${{ secrets.SPACEFAST_DEPLOY_KEY }}
SPACEFAST_API_URL: https://api.spacefast.com
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ node_modules/
dist/
build/
.stattic/
.spacefast/
.astro/
.DS_Store
*.log
.gstack/
175 changes: 51 additions & 124 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,132 +1,59 @@
# Spacefast Examples
# Spacefast examples

The canonical home for Spacefast examples. It contains polished, copy-paste
recipes and runnable platform apps. Do not copy examples into the product
monorepo or create one-off repositories for them.
Small, complete projects for learning Spacefast, testing framework support, and
moving an existing site without starting over.

## Layout
Every directory is independent. Clone the repository, enter one example, and
let the CLI detect the framework, install dependencies, build, and publish:

```text
comments/ ← minimal Zero comments app
kitchen-sink/ ← one root app mixing Pages, Zero, PHP, TypeScript, and JavaScript
examples/
<slug>/
prompt.md ← the copy-paste recipe prompt
meta.json ← gallery metadata and live URL
site/ ← the built site or buildable project
README.md ← implementation notes and live link
```sh
cd astro-with-wordpress
npm install
npm run dev # the framework's own dev server
sf publish
```

The root apps are direct, runnable references. Each app contains its own
`sf.jsonc`; run `sf dev` or `sf publish` from that directory.
Use `sf build` when you only want to prove the deployable artifact locally.
`sf dev` is the local server for Zero apps (`zero/`, `kitchen-sink/`); for
everything else, use the framework's dev server.

## Catalog
## Directory map

Runtime and build-based projects come first. Straight-up static HTML follows.

| Example | Type |
| Directory | What it proves |
| --- | --- |
| [`kitchen-sink/`](./kitchen-sink/) | Mixed Zero + PHP + TypeScript + JavaScript |
| [`comments/`](./comments/) | Zero runtime |
| [Realtime Zero app](./examples/zero-perfect/) | Zero runtime |
| [Annual report](./examples/report/) | Build project |
| [App prototype](./examples/app/) | Build project |
| [Blog](./examples/blog/) | Build project |
| [Conference site](./examples/conference/) | Build project |
| [Dev-tool landing](./examples/cli/) | Build project |
| [Digital garden](./examples/notes/) | Build project |
| [Docs site](./examples/docs/) | Build project |
| [Habit tracker](./examples/habits/) | Build project |
| [Party invite](./examples/party/) | Build project |
| [Photo portfolio](./examples/photos/) | Build project |
| [Real-estate listing](./examples/listing/) | Build project |
| [Recipe blog](./examples/recipes/) | Build project |
| [Revenue analysis](./examples/revenue-review/) | Build project |
| [SaaS landing](./examples/saas/) | Build project |
| [Sales proposal](./examples/proposal/) | Build project |
| [Wedding invite](./examples/invitation/) | Build project |
| [WordPress + Astro](./examples/wp-astro/) | Build project |
| [WordPress + React](./examples/wp-react/) | Build project |
| [2048](./examples/2048/) | Static HTML |
| [Arcade](./examples/arcade/) | Static HTML |
| [Band site](./examples/band/) | Static HTML |
| [Barbershop](./examples/barber/) | Static HTML |
| [Brick-breaker game](./examples/game/) | Static HTML |
| [Calorie tracker](./examples/calories/) | Static HTML |
| [Changelog](./examples/changelog/) | Static HTML |
| [Coming-soon](./examples/soon/) | Static HTML |
| [Community garden](./examples/garden/) | Static HTML |
| [Design studio](./examples/studio/) | Static HTML |
| [Food truck](./examples/foodtruck/) | Static HTML |
| [Fundraiser](./examples/fundraiser/) | Static HTML |
| [Generative art](./examples/generative-art/) | Static HTML |
| [Growth dashboard](./examples/dashboard/) | Static HTML |
| [Hackathon](./examples/hackathon/) | Static HTML |
| [Link-in-bio](./examples/links/) | Static HTML |
| [Memorial](./examples/memorial/) | Static HTML |
| [Newsletter](./examples/newsletter/) | Static HTML |
| [Pitch deck](./examples/deck/) | Static HTML |
| [Portfolio](./examples/portfolio/) | Static HTML |
| [QR landing](./examples/qr/) | Static HTML |
| [ROI calculator](./examples/roi/) | Static HTML |
| [Restaurant menu](./examples/menu/) | Static HTML |
| [Résumé](./examples/resume/) | Static HTML |
| [Savings calculator](./examples/savings/) | Static HTML |
| [Status page](./examples/status/) | Static HTML |
| [Technical planning](./examples/technical-plan/) | Static HTML |
| [Tip splitter](./examples/tip/) | Static HTML |
| [Web zine](./examples/zine/) | Static HTML |
| [Word game](./examples/word-game/) | Static HTML |

## Recipes

The recipes cover a band site, a calorie tracker, a playable game, a restaurant
menu, a technical plan, and more. Each recipe ships with the live website its
prompt produces.

Every recipe exists to answer one question: _given the prompt, can any AI agent
build my version and publish it to Spacefast?_ They power the public recipe gallery
at [spacefast.com/recipes](https://spacefast.com/recipes).

This historically named repository is the canonical source for Recipes. Prompts and metadata
are compiled into a public JSON feed by GitHub Actions:

<https://spacefast.github.io/examples/manifest.json>

The Spacefast website and the badges on live recipe outputs read that feed. Do not
copy prompts or gallery metadata into another repository.

## The badge

Every published recipe output loads the shared Spacefast badge. The panel is always
open, and the shared script reads the current prompt from the canonical feed:

```html
<script src="https://spacefast.com/badge.js" data-example="<slug>"></script>
```

Do not vendor `badge.js` or embed a prompt in a recipe build. Keeping the badge
shared means a prompt or badge improvement reaches every output without another
site publish. The catalog intentionally retains the historical `data-example`
attribute until the production shared badge deploys `data-recipe` support; this keeps
new and already-published outputs functional throughout the rollout.

## Publishing

GitHub Actions (`.github/workflows/publish.yml`) validates the full catalog,
publishes the JSON feed to GitHub Pages, and rebuilds and publishes changed
recipe outputs to their existing Spacefast spaces. It authenticates with the
team-owned `SPACEFAST_DEPLOY_KEY` repository secret. Static recipe outputs are
published as-is; recipes with a `package.json` are built first. A Spacefast Zero
project declares `"runtime": "zero"` in `meta.json`; the workflow compiles it
and publishes its `site/` project root so the server artifact is included.

To add a recipe: copy `TEMPLATE.md` into `examples/<slug>/prompt.md`, fill it in,
drop the site in `examples/<slug>/site/`, and add `meta.json` using
`meta.example.json` as the schema. Run `bun test scripts/catalog.test.mjs` locally;
CI runs the same catalog and output validation and handles the rest.

The directory slug is also the Spacefast space slug by default. If that hostname
is reserved or the recipe deliberately publishes elsewhere, add `publish_slug`
to `meta.json` and make `live_url` match it. The recipe route and badge continue
to use the directory slug.
| [`jekyll/`](./jekyll/) | Ruby/Bundler detection, Jekyll build, and `_site` output |
| [`gatsby/`](./gatsby/) | Gatsby detection and `public` output |
| [`zero/`](./zero/) | The smallest useful Spacefast Zero app |
| [`blog-with-custom-templates/`](./blog-with-custom-templates/) | Static publishing plus `_layout.html`, `_pages`, `theme.json`, and `sf.jsonc` |
| [`nextjs-static/`](./nextjs-static/) | A Next.js static export served entirely as files |
| [`nextjs/`](./nextjs/) | Server-rendered Next.js, route handlers, and automatic OpenNext packaging |
| [`astro-with-wordpress/`](./astro-with-wordpress/) | Astro built from WordPress through `@spacefast/wordpress` |
| [`functions-php/`](./functions-php/) | Native PHP Functions with JSON, form bodies, validation, and verified auth context |
| [`recipes/`](./recipes/) | Canonical prompts, metadata, live outputs, and the public recipe feed |
| [`kitchen-sink/`](./kitchen-sink/) | Zero, Pages, PHP, JavaScript, TypeScript, auth, mail, and routing in one project |
| [`migrate-from-cloudflare-pages/`](./migrate-from-cloudflare-pages/) | Wrangler build settings and Pages convention files |
| [`migrate-from-vercel/`](./migrate-from-vercel/) | `vercel.json` build, redirects, rewrites, headers, and cron import |
| [`migrate-from-netlify/`](./migrate-from-netlify/) | A full Netlify project, including Functions, plugins, contexts, forms, and the explicit ports they require |

Each example README calls out the important files, the expected build output,
and any product boundary that cannot be translated automatically.

## Recipes integration

`recipes/` is the source of truth for [spacefast.com/recipes](https://spacefast.com/recipes).
Each recipe contains its copy-paste prompt, gallery metadata, runnable output,
and live URL. GitHub Actions compiles those records into the historical public
feed at <https://spacefast.github.io/examples/manifest.json> and publishes only
changed recipe outputs.

Do not duplicate recipe prompts in another package or repository. Framework
examples teach an integration; recipes are user-facing starting points that an
agent can customize and publish.

## Validation

The pull-request workflow builds every top-level project through `sf build`,
runs the Zero app tests, validates every recipe output, and builds the recipe
manifest, without creating a live version. `migrate-from-netlify` builds its
prebuilt output, because `sf build` has no flag to accept the Netlify features
that still need a port.
1 change: 1 addition & 0 deletions astro-with-wordpress/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
WORDPRESS_URL=https://wordpress.org/news
5 changes: 5 additions & 0 deletions astro-with-wordpress/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
dist/
.astro/
node_modules/
.spacefast/
.env
Loading
Loading