mirror of
https://github.com/nexu-io/open-design.git
synced 2026-06-01 03:14:35 +07:00
* feat(plugins): site-wide plugin detail pages, share-to-site links, landing deploy trigger Why: a merged plugin PR didn't redeploy the landing site (plugins/** was missing from the deploy paths), and the desktop Share menu copied a local/404 link instead of the public marketplace URL. The landing plugin routing left by the detail-page rework also 404'd: the locale listing's cards used a multi-segment href while detail pages were single-segment, and only 388 bundled _official plugins had pages. What changed: - Deploy: landing-page deploy/ci trigger on plugins/**, and skip the slow previews step on an exact cache hit (cache key aligned across both workflows so a PR-built cache is reused by main). - Share URL: packages/contracts/plugin-url.ts owns the single-segment plugin URL scheme; the web Share menu and the landing site both derive links from it. Web links now point at https://open-design.ai/plugins/<slug>/. - Full detail coverage: detail pages now cover all 403 local plugins (_official incl. atoms + community), each rendered from its local manifest. Fixes the locale-listing 404s and the community manifest-name/catalog-id (- vs /) mismatch. - Self-host: daemon exposes OD_SITE_ORIGIN via /api/app-config; web falls back to the canonical origin until the daemon answers. Validation: pnpm guard, pnpm typecheck (all packages), contracts + web tests green, and a full build E2E confirming all 403 catalog ids and locale-listing cards resolve to built detail pages (0 missing). * chore: retrigger CI * ci(landing): carry plugins/** trigger + previews cache-hit into #2994 split workflows Merged origin/main, which split landing deploy into staging + manual production (#2994). git auto-migrated my landing-page-deploy.yml changes into landing-page-staging.yml via rename detection (plugins/** path, fallback-preview-card.ts cache key, cache-hit skip all carried). The new manual landing-page-production.yml didn't have them, so add the previews cache-key alignment + cache-hit skip there too (plugins/** path is N/A — production is workflow_dispatch only). * fix(ci): wrangler-action uses pnpm so it tolerates landing's workspace dep This PR added @open-design/contracts (workspace:*) to apps/landing-page/package.json so the landing site can share the plugin-url slug rules. But the landing deploy/preview steps run cloudflare/wrangler-action with packageManager: npm in workingDirectory apps/landing-page, and 'npm i wrangler' chokes on the workspace: protocol (EUNSUPPORTEDPROTOCOL), failing 'Validate landing page'. Switch all three landing wrangler-action steps (staging / ci preview / production) to packageManager: pnpm, which is workspace-aware. * test(e2e): bundled plugins now offer the README badge After this branch, buildPluginShareUrl returns a public open-design.ai link for bundled plugins (not just official-marketplace ones), so the home-starter share menu now shows 'Copy README badge'. Update the assertion from toHaveCount(0) to toBeVisible(). * fix(landing): drop @open-design/contracts dep, use a landing-local slug helper Per review on #2999: the marketing site must not import @open-design/contracts (AGENTS.md boundary — it's the web/daemon product-runtime contract layer). Move the slug/path helpers into landing-local app/_lib/plugin-slug.ts; the web client keeps contracts' plugin-url. The two derive the same scheme and are verified in lockstep by the e2e route check (403 share URLs -> 403 detail pages, 0 missing). landing no longer has a workspace dep, so revert the wrangler-action packageManager back to npm. * fix(landing): include plugins/_official in previews cache key Per review on #2999: generate-previews.ts builds bundled-plugin preview jobs from plugins/_official/**/open-design.json and renders fallback cards from manifest fields (title/description/mode/scenario/tags). With plugins/** now triggering the workflow but the cache key not hashing plugin inputs, a plugin-only PR/merge could exact-hit an old cache and skip the preview regen, shipping with a stale or missing /previews/plugins/<manifest-id>.png. Add plugins/_official/** to the cache key in all three landing workflows (ci, staging, production). community is not currently covered by generate-previews so its glob is omitted. * fix(plugins): include community marketplace installs in share gate hasPublicPage now covers sourceMarketplaceId === 'community' so the README badge and public detail link surface for community installs. Community manifest names carry a community- prefix that diverges from the landing-page route slug, so URL derivation uses sourceMarketplaceEntryName (community/<folder>) instead — pluginDetailSlug takes the last segment, matching the /plugins/<folder>/ route the landing page emits. Adds component tests for buildPluginShareUrl, badge copy, and the Open-in-marketplace link for a community/registry-starter record. Generated-By: looper 0.9.2 (runner=fixer, agent=claude-code) --------- Co-authored-by: mrcfps <mrc@powerformer.com>
161 lines
6.5 KiB
YAML
161 lines
6.5 KiB
YAML
name: landing-page-production
|
|
|
|
# Promotes the current landing page to PRODUCTION: the `open-design-landing`
|
|
# Cloudflare Pages project, served at open-design.ai. This is the ONLY
|
|
# workflow that names the production project, and it is manual-only
|
|
# (workflow_dispatch) — a merge to `main` can never reach production on its
|
|
# own; it only updates the staging project (staging.open-design.ai) via
|
|
# `landing-page-staging`. Gate this further by configuring required reviewers
|
|
# on the GitHub `production` environment (Settings → Environments).
|
|
#
|
|
# The build is identical to staging/CI, so what you reviewed on
|
|
# staging.open-design.ai is what ships.
|
|
|
|
on:
|
|
workflow_dispatch:
|
|
inputs:
|
|
reason:
|
|
description: 'Why promote now? (recorded in the run log)'
|
|
required: false
|
|
|
|
permissions:
|
|
contents: read
|
|
deployments: write
|
|
|
|
# Never cancel an in-flight production deploy.
|
|
concurrency:
|
|
group: landing-page-production
|
|
cancel-in-progress: false
|
|
|
|
jobs:
|
|
deploy:
|
|
name: Deploy landing page to production
|
|
# Production ships `main` only. workflow_dispatch can be launched from any
|
|
# ref via the Actions "Use workflow from" dropdown; gate the whole job on
|
|
# the main ref so a dispatch from a feature branch/tag is skipped outright
|
|
# (no deploy) instead of recording a non-main production run — which would
|
|
# also dodge blog-indexing's `workflow_run` `branches: [main]` filter.
|
|
if: github.repository == 'nexu-io/open-design' && github.ref == 'refs/heads/main'
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 20
|
|
environment:
|
|
name: production
|
|
url: https://open-design.ai
|
|
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v6.0.2
|
|
with:
|
|
# Production always ships `main`. workflow_dispatch can be launched
|
|
# from any ref via the Actions "Use workflow from" dropdown, so pin
|
|
# the checkout to main — the deployed artifact must equal reviewed
|
|
# main, never whatever branch/tag the operator happened to select.
|
|
ref: main
|
|
|
|
- name: Setup pnpm
|
|
uses: pnpm/action-setup@v5
|
|
with:
|
|
version: 10.33.2
|
|
|
|
- name: Setup Node.js
|
|
uses: actions/setup-node@v6
|
|
with:
|
|
node-version: 24
|
|
cache: pnpm
|
|
|
|
- name: Install dependencies
|
|
run: pnpm install --frozen-lockfile
|
|
|
|
- name: Resolve Playwright version
|
|
id: playwright-version
|
|
run: |
|
|
version=$(node -p "require('./apps/landing-page/package.json').devDependencies.playwright.replace(/[^0-9.]/g,'')")
|
|
echo "version=$version" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Cache generated previews
|
|
id: previews-cache
|
|
uses: actions/cache@v5.0.5
|
|
with:
|
|
path: apps/landing-page/public/previews
|
|
key: landing-page-previews-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml', 'package.json', 'apps/landing-page/package.json', 'apps/landing-page/scripts/generate-previews.ts', 'apps/landing-page/scripts/fallback-preview-card.ts', 'skills/**', 'design-templates/**', 'templates/live-artifacts/**', 'plugins/_official/**') }}
|
|
restore-keys: |
|
|
landing-page-previews-${{ runner.os }}-
|
|
|
|
- name: Cache Playwright browsers
|
|
uses: actions/cache@v5.0.5
|
|
with:
|
|
path: ~/.cache/ms-playwright
|
|
key: playwright-${{ runner.os }}-${{ steps.playwright-version.outputs.version }}
|
|
|
|
- name: Install Playwright Chromium
|
|
run: pnpm --filter @open-design/landing-page exec playwright install --with-deps chromium
|
|
|
|
- name: Typecheck landing page
|
|
run: pnpm --filter @open-design/landing-page typecheck
|
|
|
|
# Generate previews before build so they end up in `out/previews/`.
|
|
# Soft vs. hard failure is enforced inside the script itself:
|
|
# individual broken `example.html` entries are logged and skipped,
|
|
# but a systemic failure (chromium launch error, every job failing)
|
|
# exits non-zero so we don't silently ship a deploy with zero
|
|
# thumbnails to production.
|
|
- name: Generate skill + template previews
|
|
# Exact previews-cache hit ⇒ public/previews already holds the correct
|
|
# thumbnails, skip the slow Playwright render. A restore-keys partial
|
|
# hit keeps cache-hit false, so we still regenerate — no stale-thumbnail
|
|
# drift.
|
|
if: steps.previews-cache.outputs.cache-hit != 'true'
|
|
run: pnpm --filter @open-design/landing-page previews
|
|
|
|
- name: Build landing page
|
|
env:
|
|
PUBLIC_GA_MEASUREMENT_ID: ${{ vars.PUBLIC_GA_MEASUREMENT_ID }}
|
|
run: pnpm --filter @open-design/landing-page build:static
|
|
|
|
- name: Verify zero external JavaScript
|
|
run: |
|
|
node <<'NODE'
|
|
const { readFileSync } = require('node:fs');
|
|
const html = readFileSync('apps/landing-page/out/index.html', 'utf8');
|
|
const forbidden = [
|
|
/<script\b[^>]*\bsrc=/i,
|
|
/type=["']module["']/i,
|
|
/\/_astro\/[^"'<>\s]+\.js/i,
|
|
];
|
|
for (const pattern of forbidden) {
|
|
if (pattern.test(html)) {
|
|
console.error(`Unexpected client JavaScript matched ${pattern}`);
|
|
process.exit(1);
|
|
}
|
|
}
|
|
NODE
|
|
|
|
- name: Verify Cloudflare image resizing URLs
|
|
run: |
|
|
node <<'NODE'
|
|
const { readFileSync } = require('node:fs');
|
|
const html = readFileSync('apps/landing-page/out/index.html', 'utf8');
|
|
const resizedUrls = html.match(/https:\/\/static\.open-design\.ai\/cdn-cgi\/image\//g) ?? [];
|
|
if (resizedUrls.length < 16) {
|
|
console.error(`Expected at least 16 Cloudflare resized image URLs, found ${resizedUrls.length}`);
|
|
process.exit(1);
|
|
}
|
|
if (/(?:src|content)=["']\/assets\/[A-Za-z0-9_.-]+\.png/.test(html)) {
|
|
console.error('Found local /assets/*.png image reference in generated landing HTML.');
|
|
process.exit(1);
|
|
}
|
|
NODE
|
|
|
|
# `--branch=main` IS the Cloudflare Pages production branch, so this
|
|
# publishes to the production domain (open-design.ai).
|
|
- name: Deploy to Cloudflare Pages (production)
|
|
uses: cloudflare/wrangler-action@v3
|
|
with:
|
|
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
|
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
|
workingDirectory: apps/landing-page
|
|
packageManager: npm
|
|
command: >
|
|
pages deploy out
|
|
--project-name=open-design-landing
|
|
--branch=main
|