Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ad5c811a88 |
@@ -0,0 +1,87 @@
|
|||||||
|
name: ci
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
# Release validation is owned by the release workflows rather than this CI
|
||||||
|
# workflow: `release-stable` has a verify job before publishing, and
|
||||||
|
# `release-beta` builds from its selected release commit. Keep this trigger
|
||||||
|
# focused on PRs, main, and manual reruns instead of duplicating tag/release
|
||||||
|
# events that would run after those release workflows have already selected
|
||||||
|
# or validated their commit.
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ci-${{ github.event.pull_request.number || github.ref }}
|
||||||
|
# Prefer current-head signal over preserving superseded logs: PR authors often
|
||||||
|
# push fixups while this workflow is still running, and stale runs can report
|
||||||
|
# failures for commits reviewers no longer need to evaluate. Release workflows
|
||||||
|
# use cancel-in-progress: false where preserving build evidence matters more.
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
validate:
|
||||||
|
name: Validate workspace
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 30
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v6.0.2
|
||||||
|
|
||||||
|
- 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
|
||||||
|
|
||||||
|
# `scripts/postinstall.mjs` only prebuilds package/tool entrypoints that
|
||||||
|
# are needed immediately after install for linked bins and shared
|
||||||
|
# sidecar/platform imports. It intentionally skips app outputs because
|
||||||
|
# building all apps would make every install run a Next/Electron-adjacent
|
||||||
|
# app build, even when a developer only needs packages/tools.
|
||||||
|
#
|
||||||
|
# Fresh CI typecheck/test still need these specific generated declarations:
|
||||||
|
# - `apps/daemon/dist/*.d.ts` for e2e runtime-adapter tests that import
|
||||||
|
# daemon runtime modules
|
||||||
|
# - `apps/desktop/dist/main/index.d.ts` for `apps/packaged` imports of
|
||||||
|
# `@open-design/desktop/main`
|
||||||
|
# - `apps/web/dist/sidecar/index.d.ts` for `apps/packaged` imports of
|
||||||
|
# `@open-design/web/sidecar`
|
||||||
|
# If postinstall grows a targeted app type-generation phase covering these
|
||||||
|
# three exports without broad app builds, this CI prebuild can be removed.
|
||||||
|
- name: Prebuild workspace type declarations
|
||||||
|
run: |
|
||||||
|
pnpm --filter @open-design/daemon build
|
||||||
|
pnpm --filter @open-design/desktop build
|
||||||
|
pnpm --filter @open-design/web build:sidecar
|
||||||
|
|
||||||
|
- name: Typecheck workspaces
|
||||||
|
run: pnpm -r --workspace-concurrency=1 --if-present run typecheck
|
||||||
|
|
||||||
|
- name: Check residual JS in TypeScript packages
|
||||||
|
run: pnpm check:residual-js
|
||||||
|
|
||||||
|
- name: Test
|
||||||
|
run: pnpm test
|
||||||
|
|
||||||
|
# Keep workspace builds serialized so generated dist output and local
|
||||||
|
# runtime artifacts are produced in a deterministic order. Parallel
|
||||||
|
# recursive builds would surface late-package failures sooner, but the
|
||||||
|
# current workspace is small enough that safer logs and fewer shared-FS
|
||||||
|
# races outweigh the lost parallelism; revisit if the package count grows.
|
||||||
|
- name: Build workspaces
|
||||||
|
run: pnpm -r --workspace-concurrency=1 --if-present run build
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
name: landing-page-ci
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
paths:
|
||||||
|
- .github/workflows/landing-page-ci.yml
|
||||||
|
- .github/workflows/landing-page.yml
|
||||||
|
- apps/landing-page/**
|
||||||
|
- package.json
|
||||||
|
- pnpm-lock.yaml
|
||||||
|
- pnpm-workspace.yaml
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
paths:
|
||||||
|
- .github/workflows/landing-page-ci.yml
|
||||||
|
- .github/workflows/landing-page.yml
|
||||||
|
- apps/landing-page/**
|
||||||
|
- package.json
|
||||||
|
- pnpm-lock.yaml
|
||||||
|
- pnpm-workspace.yaml
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: landing-page-ci-${{ github.event.pull_request.number || github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
validate:
|
||||||
|
name: Validate landing page
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 15
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v6.0.2
|
||||||
|
|
||||||
|
- 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: Typecheck landing page
|
||||||
|
run: pnpm --filter @open-design/landing-page typecheck
|
||||||
|
|
||||||
|
- name: Build landing page
|
||||||
|
run: pnpm --filter @open-design/landing-page build
|
||||||
|
|
||||||
|
- 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
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
name: landing-page-deploy
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
paths:
|
||||||
|
- .github/workflows/landing-page-deploy.yml
|
||||||
|
- .github/workflows/landing-page-ci.yml
|
||||||
|
- apps/landing-page/**
|
||||||
|
- package.json
|
||||||
|
- pnpm-lock.yaml
|
||||||
|
- pnpm-workspace.yaml
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
deployments: write
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: landing-page-deploy-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
deploy:
|
||||||
|
name: Deploy landing page
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 15
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v6.0.2
|
||||||
|
|
||||||
|
- 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: Typecheck landing page
|
||||||
|
run: pnpm --filter @open-design/landing-page typecheck
|
||||||
|
|
||||||
|
- name: Build landing page
|
||||||
|
run: pnpm --filter @open-design/landing-page build
|
||||||
|
|
||||||
|
- 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
|
||||||
|
|
||||||
|
- name: Deploy to Cloudflare Pages
|
||||||
|
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=${{ github.ref_name }}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
name: github-metrics
|
||||||
|
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
# Runs daily at 00:15 UTC; output is committed to docs/assets/github-metrics.svg.
|
||||||
|
- cron: '15 0 * * *'
|
||||||
|
workflow_dispatch:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
paths:
|
||||||
|
- .github/workflows/metrics.yml
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
pull-requests: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
metrics:
|
||||||
|
name: Generate repository metrics SVG
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Generate GitHub repository metrics
|
||||||
|
uses: lowlighter/metrics@latest
|
||||||
|
with:
|
||||||
|
# Output path; the action opens/updates a PR when this file changes.
|
||||||
|
# Requires manual review to merge. If metrics unchanged, no PR is created.
|
||||||
|
filename: docs/assets/github-metrics.svg
|
||||||
|
|
||||||
|
# Auth: METRICS_TOKEN must be a fine-grained PAT or GitHub App token that
|
||||||
|
# can create pull requests in this repository. GITHUB_TOKEN is kept only
|
||||||
|
# as a read/render fallback because many orgs disallow PR creation from it.
|
||||||
|
token: ${{ secrets.METRICS_TOKEN || secrets.GITHUB_TOKEN }}
|
||||||
|
committer_token: ${{ secrets.METRICS_TOKEN }}
|
||||||
|
output_action: pull-request
|
||||||
|
output_condition: data-changed
|
||||||
|
|
||||||
|
# Use the repository template (per-repo metrics, not user metrics).
|
||||||
|
# Organization-owned repositories must be targeted explicitly, otherwise
|
||||||
|
# lowlighter/metrics infers the token owner and treats the target as an org.
|
||||||
|
template: repository
|
||||||
|
base: ''
|
||||||
|
user: nexu-io
|
||||||
|
repo: open-design
|
||||||
|
|
||||||
|
# Plugins. Anything that requires a personal token will silently no-op
|
||||||
|
# without METRICS_TOKEN — the rest still produce a useful SVG.
|
||||||
|
plugin_contributors: yes
|
||||||
|
plugin_contributors_categories: |
|
||||||
|
{
|
||||||
|
"Skills": "skills/**",
|
||||||
|
"Design systems": "design-systems/**",
|
||||||
|
"Web": "apps/web/**",
|
||||||
|
"Daemon": "apps/daemon/**",
|
||||||
|
"Docs": "docs/**"
|
||||||
|
}
|
||||||
|
plugin_followup: yes
|
||||||
|
plugin_followup_sections: pr, issue
|
||||||
|
plugin_languages: yes
|
||||||
|
plugin_languages_details: lines, percentage
|
||||||
|
plugin_languages_limit: 8
|
||||||
|
plugin_lines: yes
|
||||||
|
plugin_traffic: yes
|
||||||
|
plugin_stargazers: yes
|
||||||
|
plugin_stargazers_charts_type: chartist
|
||||||
|
|
||||||
|
config_timezone: Asia/Shanghai
|
||||||
|
config_display: large
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
name: refresh-contributors-wall
|
||||||
|
|
||||||
|
on:
|
||||||
|
# Daily refresh keeps the contributors wall CDN cache moving even when
|
||||||
|
# contributor data changes outside pull request merges.
|
||||||
|
schedule:
|
||||||
|
- cron: '0 1 * * *'
|
||||||
|
# Manual trigger: Use when you need to force-refresh the contributors wall
|
||||||
|
# outside the daily schedule (e.g., after a bulk contributor update or
|
||||||
|
# after fixing the cache_bust pattern in README files).
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
pull-requests: write
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: refresh-contributors-wall
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
refresh:
|
||||||
|
name: Refresh contributors wall cache bust
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v6.0.2
|
||||||
|
|
||||||
|
- name: Refresh cache bust date
|
||||||
|
run: |
|
||||||
|
DATE="$(date -u +%F)"
|
||||||
|
MATCHES="$(perl -0ne '$count += () = /cache_bust=\d{4}-\d{2}-\d{2}/g; END { print $count + 0 }' README*.md)"
|
||||||
|
if [ "$MATCHES" -eq 0 ]; then
|
||||||
|
echo "Warning: No cache_bust patterns found. README format may have changed."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
perl -0pi -e "s/cache_bust=\d{4}-\d{2}-\d{2}/cache_bust=$DATE/g" README*.md
|
||||||
|
|
||||||
|
- name: Create refresh pull request
|
||||||
|
uses: peter-evans/create-pull-request@v8
|
||||||
|
with:
|
||||||
|
# Auth mirrors the metrics workflow: prefer a repository token that can
|
||||||
|
# create pull requests, with GITHUB_TOKEN as a fallback for repos where
|
||||||
|
# Actions-created PRs are allowed.
|
||||||
|
token: ${{ secrets.METRICS_TOKEN || secrets.GITHUB_TOKEN }}
|
||||||
|
add-paths: 'README*.md'
|
||||||
|
branch: automation/refresh-contributors-wall
|
||||||
|
delete-branch: true
|
||||||
|
commit-message: 'docs(readme): refresh contributors wall'
|
||||||
|
title: 'docs(readme): refresh contributors wall'
|
||||||
|
body: |
|
||||||
|
Refreshes the contributors wall cache bust date in README files.
|
||||||
|
|
||||||
|
Generated by the scheduled `refresh-contributors-wall` workflow.
|
||||||
@@ -0,0 +1,467 @@
|
|||||||
|
name: release-beta
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
signed:
|
||||||
|
description: "Build signed/notarized mac artifacts. Disable only for explicit unsigned validation releases."
|
||||||
|
required: true
|
||||||
|
type: boolean
|
||||||
|
default: true
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: open-design-release-beta
|
||||||
|
cancel-in-progress: false
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
metadata:
|
||||||
|
name: Prepare beta metadata
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ github.token }}
|
||||||
|
GITHUB_REPOSITORY: ${{ github.repository }}
|
||||||
|
OPEN_DESIGN_RELEASE_SIGNED: ${{ inputs.signed }}
|
||||||
|
outputs:
|
||||||
|
asset_version_suffix: ${{ steps.beta.outputs.asset_version_suffix }}
|
||||||
|
base_version: ${{ steps.beta.outputs.base_version }}
|
||||||
|
beta_tag: ${{ steps.beta.outputs.beta_tag }}
|
||||||
|
beta_version: ${{ steps.beta.outputs.beta_version }}
|
||||||
|
branch: ${{ steps.beta.outputs.branch }}
|
||||||
|
commit: ${{ steps.beta.outputs.commit }}
|
||||||
|
release_name: ${{ steps.beta.outputs.release_name }}
|
||||||
|
signed: ${{ steps.beta.outputs.signed }}
|
||||||
|
version_tag: ${{ steps.beta.outputs.version_tag }}
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v6.0.2
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v6
|
||||||
|
with:
|
||||||
|
node-version: 24
|
||||||
|
|
||||||
|
- name: Prepare beta release metadata
|
||||||
|
id: beta
|
||||||
|
run: node --experimental-strip-types ./scripts/release-beta.ts
|
||||||
|
|
||||||
|
build_mac:
|
||||||
|
name: Build beta mac arm64
|
||||||
|
needs: metadata
|
||||||
|
runs-on: macos-14
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ github.token }}
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v6.0.2
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- 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
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
|
- name: Apply beta package version
|
||||||
|
run: npm pkg set "version=${{ needs.metadata.outputs.beta_version }}" --prefix apps/packaged
|
||||||
|
|
||||||
|
- name: Prepare Apple signing certificate
|
||||||
|
if: ${{ inputs.signed }}
|
||||||
|
env:
|
||||||
|
APPLE_SIGNING_CERTIFICATE_BASE64: ${{ secrets.APPLE_SIGNING_CERTIFICATE_BASE64 }}
|
||||||
|
APPLE_SIGNING_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_SIGNING_CERTIFICATE_PASSWORD }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
cert_path="$RUNNER_TEMP/open-design-signing.p12"
|
||||||
|
if ! printf '%s' "$APPLE_SIGNING_CERTIFICATE_BASE64" | base64 --decode > "$cert_path" 2>/dev/null; then
|
||||||
|
printf '%s' "$APPLE_SIGNING_CERTIFICATE_BASE64" | base64 -D > "$cert_path"
|
||||||
|
fi
|
||||||
|
{
|
||||||
|
echo "CSC_LINK=$cert_path"
|
||||||
|
echo "CSC_KEY_PASSWORD=$APPLE_SIGNING_CERTIFICATE_PASSWORD"
|
||||||
|
} >> "$GITHUB_ENV"
|
||||||
|
|
||||||
|
- name: Build beta mac artifacts
|
||||||
|
env:
|
||||||
|
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||||
|
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
|
||||||
|
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
signed_flag=""
|
||||||
|
if [ "${{ inputs.signed }}" = "true" ]; then
|
||||||
|
signed_flag="--signed"
|
||||||
|
fi
|
||||||
|
pnpm exec tools-pack mac build \
|
||||||
|
--dir "$RUNNER_TEMP/tools-pack" \
|
||||||
|
--namespace release-beta \
|
||||||
|
--portable \
|
||||||
|
--to all \
|
||||||
|
--json \
|
||||||
|
$signed_flag
|
||||||
|
|
||||||
|
- name: Prepare beta assets
|
||||||
|
id: assets
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
release_dir="$RUNNER_TEMP/release-assets"
|
||||||
|
mkdir -p "$release_dir"
|
||||||
|
|
||||||
|
source_dmg="$RUNNER_TEMP/tools-pack/out/mac/namespaces/release-beta/dmg/Open Design-release-beta.dmg"
|
||||||
|
source_zip="$RUNNER_TEMP/tools-pack/out/mac/namespaces/release-beta/zip/Open Design-release-beta.zip"
|
||||||
|
if [ ! -f "$source_dmg" ]; then
|
||||||
|
echo "expected dmg not found at $source_dmg" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [ ! -f "$source_zip" ]; then
|
||||||
|
echo "expected zip not found at $source_zip" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
asset_suffix="${{ needs.metadata.outputs.asset_version_suffix }}"
|
||||||
|
versioned_dmg="open-design-${{ needs.metadata.outputs.beta_version }}${asset_suffix}-mac-arm64.dmg"
|
||||||
|
versioned_zip="open-design-${{ needs.metadata.outputs.beta_version }}${asset_suffix}-mac-arm64.zip"
|
||||||
|
dmg_checksum_file="$versioned_dmg.sha256"
|
||||||
|
zip_checksum_file="$versioned_zip.sha256"
|
||||||
|
|
||||||
|
cp "$source_dmg" "$release_dir/$versioned_dmg"
|
||||||
|
cp "$source_zip" "$release_dir/$versioned_zip"
|
||||||
|
(
|
||||||
|
cd "$release_dir"
|
||||||
|
shasum -a 256 "$versioned_dmg" > "$dmg_checksum_file"
|
||||||
|
shasum -a 256 "$versioned_zip" > "$zip_checksum_file"
|
||||||
|
)
|
||||||
|
|
||||||
|
zip_sha512="$(openssl dgst -sha512 -binary "$release_dir/$versioned_zip" | openssl base64 -A)"
|
||||||
|
zip_size="$(stat -f%z "$release_dir/$versioned_zip")"
|
||||||
|
zip_url="https://github.com/${GITHUB_REPOSITORY}/releases/download/${{ needs.metadata.outputs.version_tag }}/$versioned_zip"
|
||||||
|
release_date="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
||||||
|
cat > "$release_dir/latest-mac.yml" <<EOF
|
||||||
|
version: "${{ needs.metadata.outputs.beta_version }}"
|
||||||
|
files:
|
||||||
|
- url: "$zip_url"
|
||||||
|
sha512: "$zip_sha512"
|
||||||
|
size: $zip_size
|
||||||
|
path: "$zip_url"
|
||||||
|
sha512: "$zip_sha512"
|
||||||
|
releaseDate: "$release_date"
|
||||||
|
releaseNotes: "Open Design beta ${{ needs.metadata.outputs.beta_version }}${asset_suffix}"
|
||||||
|
EOF
|
||||||
|
|
||||||
|
- name: Upload mac release bundle
|
||||||
|
uses: actions/upload-artifact@v7
|
||||||
|
with:
|
||||||
|
name: open-design-beta-mac-release-assets
|
||||||
|
path: ${{ runner.temp }}/release-assets
|
||||||
|
|
||||||
|
build_win:
|
||||||
|
name: Build beta win x64
|
||||||
|
needs: metadata
|
||||||
|
runs-on: windows-latest
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ github.token }}
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v6.0.2
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- 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
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
|
- name: Apply beta package version
|
||||||
|
run: npm pkg set "version=${{ needs.metadata.outputs.beta_version }}" --prefix apps/packaged
|
||||||
|
|
||||||
|
- name: Build beta windows artifacts
|
||||||
|
shell: pwsh
|
||||||
|
run: >-
|
||||||
|
pnpm exec tools-pack win build
|
||||||
|
--dir "${{ runner.temp }}/tools-pack"
|
||||||
|
--namespace release-beta-win
|
||||||
|
--portable
|
||||||
|
--to nsis
|
||||||
|
--json
|
||||||
|
|
||||||
|
- name: Prepare windows beta assets
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
$releaseDir = Join-Path $env:RUNNER_TEMP "release-assets"
|
||||||
|
New-Item -ItemType Directory -Force -Path $releaseDir | Out-Null
|
||||||
|
|
||||||
|
$sourceInstaller = Join-Path $env:RUNNER_TEMP "tools-pack/out/win/namespaces/release-beta-win/builder/Open Design-release-beta-win-setup.exe"
|
||||||
|
$sourceBlockmap = Join-Path $env:RUNNER_TEMP "tools-pack/out/win/namespaces/release-beta-win/builder/Open Design-release-beta-win-setup.exe.blockmap"
|
||||||
|
if (!(Test-Path $sourceInstaller)) {
|
||||||
|
throw "expected installer not found at $sourceInstaller"
|
||||||
|
}
|
||||||
|
if (!(Test-Path $sourceBlockmap)) {
|
||||||
|
throw "expected blockmap not found at $sourceBlockmap"
|
||||||
|
}
|
||||||
|
|
||||||
|
$windowsAssetSuffix = ".unsigned"
|
||||||
|
$versionedInstaller = "open-design-${{ needs.metadata.outputs.beta_version }}$windowsAssetSuffix-win-x64-setup.exe"
|
||||||
|
$versionedBlockmap = "open-design-${{ needs.metadata.outputs.beta_version }}$windowsAssetSuffix-win-x64-setup.exe.blockmap"
|
||||||
|
$checksumFile = "$versionedInstaller.sha256"
|
||||||
|
Copy-Item $sourceInstaller (Join-Path $releaseDir $versionedInstaller)
|
||||||
|
Copy-Item $sourceBlockmap (Join-Path $releaseDir $versionedBlockmap)
|
||||||
|
|
||||||
|
$installerPath = Join-Path $releaseDir $versionedInstaller
|
||||||
|
$hash = (Get-FileHash -Path $installerPath -Algorithm SHA256).Hash.ToLowerInvariant()
|
||||||
|
"$hash $versionedInstaller" | Set-Content -Path (Join-Path $releaseDir $checksumFile)
|
||||||
|
$installerBytes = [System.IO.File]::ReadAllBytes($installerPath)
|
||||||
|
$installerSha512 = [System.Convert]::ToBase64String([System.Security.Cryptography.SHA512]::Create().ComputeHash($installerBytes))
|
||||||
|
$installerSize = (Get-Item $installerPath).Length
|
||||||
|
$installerUrl = "https://github.com/$env:GITHUB_REPOSITORY/releases/download/${{ needs.metadata.outputs.version_tag }}/$versionedInstaller"
|
||||||
|
$releaseDate = [DateTime]::UtcNow.ToString("yyyy-MM-ddTHH:mm:ssZ")
|
||||||
|
@(
|
||||||
|
'version: "${{ needs.metadata.outputs.beta_version }}"'
|
||||||
|
'files:'
|
||||||
|
" - url: `"$installerUrl`""
|
||||||
|
" sha512: `"$installerSha512`""
|
||||||
|
" size: $installerSize"
|
||||||
|
"path: `"$installerUrl`""
|
||||||
|
"sha512: `"$installerSha512`""
|
||||||
|
"releaseDate: `"$releaseDate`""
|
||||||
|
"releaseNotes: `"Open Design beta ${{ needs.metadata.outputs.beta_version }}$windowsAssetSuffix`""
|
||||||
|
) | Set-Content -Path (Join-Path $releaseDir "latest.yml")
|
||||||
|
|
||||||
|
- name: Upload windows release bundle
|
||||||
|
uses: actions/upload-artifact@v7
|
||||||
|
with:
|
||||||
|
name: open-design-beta-win-release-assets
|
||||||
|
path: ${{ runner.temp }}/release-assets
|
||||||
|
|
||||||
|
build_linux:
|
||||||
|
name: Build beta linux x64
|
||||||
|
needs: metadata
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ github.token }}
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v6.0.2
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- 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
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
|
- name: Apply beta package version
|
||||||
|
env:
|
||||||
|
BETA_VERSION: ${{ needs.metadata.outputs.beta_version }}
|
||||||
|
run: npm pkg set "version=$BETA_VERSION" --prefix apps/packaged
|
||||||
|
|
||||||
|
# `--containerized` builds the AppImage inside the electronuserland/builder
|
||||||
|
# Docker image (glibc 2.27 baseline) so the resulting binary runs on older
|
||||||
|
# distros than ubuntu-latest's glibc 2.39. Docker is preinstalled on the
|
||||||
|
# GitHub-hosted ubuntu-latest runner, so no extra setup is required.
|
||||||
|
- name: Build beta linux artifacts
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
pnpm exec tools-pack linux build \
|
||||||
|
--dir "$RUNNER_TEMP/tools-pack" \
|
||||||
|
--namespace release-beta-linux \
|
||||||
|
--portable \
|
||||||
|
--to appimage \
|
||||||
|
--containerized \
|
||||||
|
--json
|
||||||
|
|
||||||
|
- name: Prepare linux beta assets
|
||||||
|
env:
|
||||||
|
BETA_VERSION: ${{ needs.metadata.outputs.beta_version }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
release_dir="$RUNNER_TEMP/release-assets"
|
||||||
|
mkdir -p "$release_dir"
|
||||||
|
|
||||||
|
source_appimage="$RUNNER_TEMP/tools-pack/out/linux/namespaces/release-beta-linux/builder/Open Design-release-beta-linux.AppImage"
|
||||||
|
if [ ! -f "$source_appimage" ]; then
|
||||||
|
echo "expected AppImage not found at $source_appimage" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Linux currently has no signing path in tools-pack, so the suffix is
|
||||||
|
# hardcoded to .unsigned (matching the windows convention above).
|
||||||
|
linux_asset_suffix=".unsigned"
|
||||||
|
versioned_appimage="open-design-${BETA_VERSION}${linux_asset_suffix}-linux-x64.AppImage"
|
||||||
|
checksum_file="$versioned_appimage.sha256"
|
||||||
|
|
||||||
|
cp "$source_appimage" "$release_dir/$versioned_appimage"
|
||||||
|
(
|
||||||
|
cd "$release_dir"
|
||||||
|
sha256sum "$versioned_appimage" > "$checksum_file"
|
||||||
|
)
|
||||||
|
|
||||||
|
- name: Upload linux release bundle
|
||||||
|
uses: actions/upload-artifact@v7
|
||||||
|
with:
|
||||||
|
name: open-design-beta-linux-release-assets
|
||||||
|
path: ${{ runner.temp }}/release-assets
|
||||||
|
|
||||||
|
publish:
|
||||||
|
name: Publish beta release
|
||||||
|
needs:
|
||||||
|
- metadata
|
||||||
|
- build_mac
|
||||||
|
- build_win
|
||||||
|
- build_linux
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ github.token }}
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v6.0.2
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Download mac release bundle
|
||||||
|
uses: actions/download-artifact@v8
|
||||||
|
with:
|
||||||
|
name: open-design-beta-mac-release-assets
|
||||||
|
path: ${{ runner.temp }}/release-assets/mac
|
||||||
|
|
||||||
|
- name: Download windows release bundle
|
||||||
|
uses: actions/download-artifact@v8
|
||||||
|
with:
|
||||||
|
name: open-design-beta-win-release-assets
|
||||||
|
path: ${{ runner.temp }}/release-assets/win
|
||||||
|
|
||||||
|
- name: Download linux release bundle
|
||||||
|
uses: actions/download-artifact@v8
|
||||||
|
with:
|
||||||
|
name: open-design-beta-linux-release-assets
|
||||||
|
path: ${{ runner.temp }}/release-assets/linux
|
||||||
|
|
||||||
|
- name: Move beta tags to current commit
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
git tag -f "${{ needs.metadata.outputs.version_tag }}" "$GITHUB_SHA"
|
||||||
|
git push origin "refs/tags/${{ needs.metadata.outputs.version_tag }}" --force
|
||||||
|
git tag -f "${{ needs.metadata.outputs.beta_tag }}" "$GITHUB_SHA"
|
||||||
|
git push origin "refs/tags/${{ needs.metadata.outputs.beta_tag }}" --force
|
||||||
|
|
||||||
|
- name: Write release notes
|
||||||
|
id: notes
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
version_notes_file="$RUNNER_TEMP/open-design-beta-version-notes.md"
|
||||||
|
latest_notes_file="$RUNNER_TEMP/open-design-beta-latest-notes.md"
|
||||||
|
cat > "$version_notes_file" <<EOF
|
||||||
|
## Summary
|
||||||
|
- channel: beta
|
||||||
|
- version: ${{ needs.metadata.outputs.beta_version }}
|
||||||
|
- base version: ${{ needs.metadata.outputs.base_version }}
|
||||||
|
- mac signed/notarized: ${{ needs.metadata.outputs.signed }}
|
||||||
|
- windows signed: false
|
||||||
|
- branch: ${{ needs.metadata.outputs.branch }}
|
||||||
|
- commit: ${{ needs.metadata.outputs.commit }}
|
||||||
|
|
||||||
|
This beta release ships mac arm64 DMG/update ZIP, Windows x64 NSIS installer assets, Linux x64 AppImage (no auto-update yet), checksums, and updater feed files.
|
||||||
|
EOF
|
||||||
|
cat > "$latest_notes_file" <<EOF
|
||||||
|
## Summary
|
||||||
|
- channel: beta
|
||||||
|
- latest version: ${{ needs.metadata.outputs.beta_version }}
|
||||||
|
- latest tag: ${{ needs.metadata.outputs.version_tag }}
|
||||||
|
|
||||||
|
This release is the mutable beta channel feed carrier. It should contain feed assets only: latest-mac.yml and latest.yml.
|
||||||
|
EOF
|
||||||
|
{
|
||||||
|
echo "version_notes_file=$version_notes_file"
|
||||||
|
echo "latest_notes_file=$latest_notes_file"
|
||||||
|
} >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
- name: Create or update immutable beta prerelease
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
release_dir="$RUNNER_TEMP/release-assets/mac"
|
||||||
|
all_release_dir="$RUNNER_TEMP/release-assets/all"
|
||||||
|
mkdir -p "$all_release_dir"
|
||||||
|
cp "$RUNNER_TEMP/release-assets/mac"/* "$all_release_dir/"
|
||||||
|
cp "$RUNNER_TEMP/release-assets/win"/* "$all_release_dir/"
|
||||||
|
cp "$RUNNER_TEMP/release-assets/linux"/* "$all_release_dir/"
|
||||||
|
if gh release view "${{ needs.metadata.outputs.version_tag }}" >/dev/null 2>&1; then
|
||||||
|
gh release edit "${{ needs.metadata.outputs.version_tag }}" \
|
||||||
|
--title "${{ needs.metadata.outputs.release_name }}" \
|
||||||
|
--notes-file "${{ steps.notes.outputs.version_notes_file }}" \
|
||||||
|
--prerelease
|
||||||
|
else
|
||||||
|
gh release create "${{ needs.metadata.outputs.version_tag }}" \
|
||||||
|
--target "$GITHUB_SHA" \
|
||||||
|
--title "${{ needs.metadata.outputs.release_name }}" \
|
||||||
|
--notes-file "${{ steps.notes.outputs.version_notes_file }}" \
|
||||||
|
--prerelease
|
||||||
|
fi
|
||||||
|
gh release upload "${{ needs.metadata.outputs.version_tag }}" "$all_release_dir"/* --clobber
|
||||||
|
|
||||||
|
- name: Create or update beta channel feed
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
latest_mac_path="$RUNNER_TEMP/release-assets/mac/latest-mac.yml"
|
||||||
|
latest_win_path="$RUNNER_TEMP/release-assets/win/latest.yml"
|
||||||
|
if gh release view "${{ needs.metadata.outputs.beta_tag }}" >/dev/null 2>&1; then
|
||||||
|
while IFS= read -r asset_name; do
|
||||||
|
if [ -n "$asset_name" ]; then
|
||||||
|
gh release delete-asset "${{ needs.metadata.outputs.beta_tag }}" "$asset_name" --yes
|
||||||
|
fi
|
||||||
|
done < <(gh release view "${{ needs.metadata.outputs.beta_tag }}" --json assets --jq '.assets[].name')
|
||||||
|
gh release edit "${{ needs.metadata.outputs.beta_tag }}" \
|
||||||
|
--title "Open Design Beta Latest" \
|
||||||
|
--notes-file "${{ steps.notes.outputs.latest_notes_file }}" \
|
||||||
|
--prerelease
|
||||||
|
gh release upload "${{ needs.metadata.outputs.beta_tag }}" "$latest_mac_path" "$latest_win_path" --clobber
|
||||||
|
else
|
||||||
|
gh release create "${{ needs.metadata.outputs.beta_tag }}" \
|
||||||
|
"$latest_mac_path" "$latest_win_path" \
|
||||||
|
--target "$GITHUB_SHA" \
|
||||||
|
--title "Open Design Beta Latest" \
|
||||||
|
--notes-file "${{ steps.notes.outputs.latest_notes_file }}" \
|
||||||
|
--prerelease
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Publish summary
|
||||||
|
run: |
|
||||||
|
{
|
||||||
|
echo "## Beta release"
|
||||||
|
echo "- Channel: beta"
|
||||||
|
echo "- Version: ${{ needs.metadata.outputs.beta_version }}"
|
||||||
|
echo "- Version tag: ${{ needs.metadata.outputs.version_tag }}"
|
||||||
|
echo "- Channel feed tag: ${{ needs.metadata.outputs.beta_tag }}"
|
||||||
|
echo "- mac signed/notarized: ${{ needs.metadata.outputs.signed }}"
|
||||||
|
echo "- windows signed: false"
|
||||||
|
echo "- mac assets: open-design-${{ needs.metadata.outputs.beta_version }}${{ needs.metadata.outputs.asset_version_suffix }}-mac-arm64.dmg, open-design-${{ needs.metadata.outputs.beta_version }}${{ needs.metadata.outputs.asset_version_suffix }}-mac-arm64.zip"
|
||||||
|
echo "- win assets: open-design-${{ needs.metadata.outputs.beta_version }}.unsigned-win-x64-setup.exe, open-design-${{ needs.metadata.outputs.beta_version }}.unsigned-win-x64-setup.exe.blockmap"
|
||||||
|
echo "- linux assets: open-design-${{ needs.metadata.outputs.beta_version }}.unsigned-linux-x64.AppImage"
|
||||||
|
echo "- Feeds: latest-mac.yml, latest.yml (no latest-linux.yml; AppImage updater not yet wired)"
|
||||||
|
} >> "$GITHUB_STEP_SUMMARY"
|
||||||
@@ -0,0 +1,486 @@
|
|||||||
|
name: release-stable
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
mac_signed:
|
||||||
|
description: "Build signed/notarized mac artifacts. Disable only for explicit unsigned validation releases."
|
||||||
|
required: true
|
||||||
|
type: boolean
|
||||||
|
default: true
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: open-design-release-stable
|
||||||
|
cancel-in-progress: false
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
metadata:
|
||||||
|
name: Prepare stable metadata
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ github.token }}
|
||||||
|
GITHUB_REPOSITORY: ${{ github.repository }}
|
||||||
|
outputs:
|
||||||
|
base_version: ${{ steps.stable.outputs.base_version }}
|
||||||
|
branch: ${{ steps.stable.outputs.branch }}
|
||||||
|
commit: ${{ steps.stable.outputs.commit }}
|
||||||
|
mac_signed: ${{ inputs.mac_signed }}
|
||||||
|
previous_stable: ${{ steps.stable.outputs.previous_stable }}
|
||||||
|
release_name: ${{ steps.stable.outputs.release_name }}
|
||||||
|
stable_version: ${{ steps.stable.outputs.stable_version }}
|
||||||
|
version_tag: ${{ steps.stable.outputs.version_tag }}
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v6.0.2
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v6
|
||||||
|
with:
|
||||||
|
node-version: 24
|
||||||
|
|
||||||
|
- name: Prepare stable release metadata
|
||||||
|
id: stable
|
||||||
|
run: node --experimental-strip-types ./scripts/release-stable.ts
|
||||||
|
|
||||||
|
verify:
|
||||||
|
name: Verify build (typecheck + tests)
|
||||||
|
needs: metadata
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v6.0.2
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- 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
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
|
# `scripts/postinstall.mjs` auto-builds `packages/*` and `tools/*`, but
|
||||||
|
# `apps/daemon` and `apps/desktop` are not in that list. On a fresh clone
|
||||||
|
# (every CI run), workspace typecheck fails because:
|
||||||
|
# - `e2e/scripts/runtime-adapter.e2e.live.test.ts` imports types from
|
||||||
|
# `apps/daemon/dist/*.js`
|
||||||
|
# - `apps/packaged/src/index.ts` dynamic-imports `@open-design/desktop/main`
|
||||||
|
# which resolves to `apps/desktop/dist/main/index.d.ts`
|
||||||
|
# Build them explicitly here. Keeps the root `typecheck` script untouched.
|
||||||
|
- name: Build daemon and desktop (typecheck dependencies)
|
||||||
|
run: |
|
||||||
|
pnpm --filter @open-design/daemon build
|
||||||
|
pnpm --filter @open-design/desktop build
|
||||||
|
|
||||||
|
- name: Typecheck workspaces
|
||||||
|
run: pnpm -r --workspace-concurrency=1 --if-present run typecheck
|
||||||
|
|
||||||
|
- name: Check residual JS in TypeScript packages
|
||||||
|
run: pnpm check:residual-js
|
||||||
|
|
||||||
|
# Workspace tests are intentionally not gated here. apps/web's
|
||||||
|
# i18n content-coverage tests assert that every locale carries
|
||||||
|
# display metadata for every prompt template / skill / design
|
||||||
|
# system. Those tests fail on `main` as of this writing because
|
||||||
|
# PR #187 added two new prompt templates without translating
|
||||||
|
# their metadata into the 9 ship-ready locales — an i18n drift
|
||||||
|
# that's out of scope for the release infrastructure. Tracked as
|
||||||
|
# a follow-up; revisit once locale metadata is back in sync.
|
||||||
|
|
||||||
|
build_mac:
|
||||||
|
name: Build stable mac arm64
|
||||||
|
needs: [metadata, verify]
|
||||||
|
runs-on: macos-14
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ github.token }}
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v6.0.2
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- 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
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
|
- name: Prepare Apple signing certificate
|
||||||
|
if: ${{ inputs.mac_signed }}
|
||||||
|
env:
|
||||||
|
APPLE_SIGNING_CERTIFICATE_BASE64: ${{ secrets.APPLE_SIGNING_CERTIFICATE_BASE64 }}
|
||||||
|
APPLE_SIGNING_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_SIGNING_CERTIFICATE_PASSWORD }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
cert_path="$RUNNER_TEMP/open-design-signing.p12"
|
||||||
|
if ! printf '%s' "$APPLE_SIGNING_CERTIFICATE_BASE64" | base64 --decode > "$cert_path" 2>/dev/null; then
|
||||||
|
printf '%s' "$APPLE_SIGNING_CERTIFICATE_BASE64" | base64 -D > "$cert_path"
|
||||||
|
fi
|
||||||
|
{
|
||||||
|
echo "CSC_LINK=$cert_path"
|
||||||
|
echo "CSC_KEY_PASSWORD=$APPLE_SIGNING_CERTIFICATE_PASSWORD"
|
||||||
|
} >> "$GITHUB_ENV"
|
||||||
|
|
||||||
|
- name: Build stable mac artifacts
|
||||||
|
env:
|
||||||
|
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||||
|
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
|
||||||
|
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
signed_flag=""
|
||||||
|
if [ "${{ inputs.mac_signed }}" = "true" ]; then
|
||||||
|
signed_flag="--signed"
|
||||||
|
fi
|
||||||
|
pnpm exec tools-pack mac build \
|
||||||
|
--dir "$RUNNER_TEMP/tools-pack" \
|
||||||
|
--namespace release-stable \
|
||||||
|
--portable \
|
||||||
|
--to all \
|
||||||
|
--json \
|
||||||
|
$signed_flag
|
||||||
|
|
||||||
|
- name: Prepare stable mac assets
|
||||||
|
id: assets
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
release_dir="$RUNNER_TEMP/release-assets"
|
||||||
|
mkdir -p "$release_dir"
|
||||||
|
|
||||||
|
source_dmg="$RUNNER_TEMP/tools-pack/out/mac/namespaces/release-stable/dmg/Open Design-release-stable.dmg"
|
||||||
|
source_zip="$RUNNER_TEMP/tools-pack/out/mac/namespaces/release-stable/zip/Open Design-release-stable.zip"
|
||||||
|
if [ ! -f "$source_dmg" ]; then
|
||||||
|
echo "expected dmg not found at $source_dmg" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [ ! -f "$source_zip" ]; then
|
||||||
|
echo "expected zip not found at $source_zip" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
versioned_dmg="open-design-${{ needs.metadata.outputs.stable_version }}-mac-arm64.dmg"
|
||||||
|
versioned_zip="open-design-${{ needs.metadata.outputs.stable_version }}-mac-arm64.zip"
|
||||||
|
dmg_checksum_file="$versioned_dmg.sha256"
|
||||||
|
zip_checksum_file="$versioned_zip.sha256"
|
||||||
|
|
||||||
|
cp "$source_dmg" "$release_dir/$versioned_dmg"
|
||||||
|
cp "$source_zip" "$release_dir/$versioned_zip"
|
||||||
|
(
|
||||||
|
cd "$release_dir"
|
||||||
|
shasum -a 256 "$versioned_dmg" > "$dmg_checksum_file"
|
||||||
|
shasum -a 256 "$versioned_zip" > "$zip_checksum_file"
|
||||||
|
)
|
||||||
|
|
||||||
|
zip_sha512="$(openssl dgst -sha512 -binary "$release_dir/$versioned_zip" | openssl base64 -A)"
|
||||||
|
zip_size="$(stat -f%z "$release_dir/$versioned_zip")"
|
||||||
|
zip_url="https://github.com/${GITHUB_REPOSITORY}/releases/download/${{ needs.metadata.outputs.version_tag }}/$versioned_zip"
|
||||||
|
release_date="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
||||||
|
cat > "$release_dir/latest-mac.yml" <<EOF
|
||||||
|
version: "${{ needs.metadata.outputs.stable_version }}"
|
||||||
|
files:
|
||||||
|
- url: "$zip_url"
|
||||||
|
sha512: "$zip_sha512"
|
||||||
|
size: $zip_size
|
||||||
|
path: "$zip_url"
|
||||||
|
sha512: "$zip_sha512"
|
||||||
|
releaseDate: "$release_date"
|
||||||
|
releaseNotes: "Open Design ${{ needs.metadata.outputs.stable_version }}"
|
||||||
|
EOF
|
||||||
|
|
||||||
|
- name: Upload mac release bundle
|
||||||
|
uses: actions/upload-artifact@v7
|
||||||
|
with:
|
||||||
|
name: open-design-stable-mac-release-assets
|
||||||
|
path: ${{ runner.temp }}/release-assets
|
||||||
|
|
||||||
|
build_win:
|
||||||
|
name: Build stable win x64
|
||||||
|
needs: [metadata, verify]
|
||||||
|
runs-on: windows-latest
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ github.token }}
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v6.0.2
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- 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
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
|
- name: Build stable windows artifacts
|
||||||
|
shell: pwsh
|
||||||
|
run: >-
|
||||||
|
pnpm exec tools-pack win build
|
||||||
|
--dir "${{ runner.temp }}/tools-pack"
|
||||||
|
--namespace release-stable-win
|
||||||
|
--portable
|
||||||
|
--to nsis
|
||||||
|
--json
|
||||||
|
|
||||||
|
- name: Prepare windows stable assets
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
$releaseDir = Join-Path $env:RUNNER_TEMP "release-assets"
|
||||||
|
New-Item -ItemType Directory -Force -Path $releaseDir | Out-Null
|
||||||
|
|
||||||
|
$sourceInstaller = Join-Path $env:RUNNER_TEMP "tools-pack/out/win/namespaces/release-stable-win/builder/Open Design-release-stable-win-setup.exe"
|
||||||
|
$sourceBlockmap = Join-Path $env:RUNNER_TEMP "tools-pack/out/win/namespaces/release-stable-win/builder/Open Design-release-stable-win-setup.exe.blockmap"
|
||||||
|
if (!(Test-Path $sourceInstaller)) {
|
||||||
|
throw "expected installer not found at $sourceInstaller"
|
||||||
|
}
|
||||||
|
if (!(Test-Path $sourceBlockmap)) {
|
||||||
|
throw "expected blockmap not found at $sourceBlockmap"
|
||||||
|
}
|
||||||
|
|
||||||
|
$versionedInstaller = "open-design-${{ needs.metadata.outputs.stable_version }}-win-x64-setup.exe"
|
||||||
|
$versionedBlockmap = "open-design-${{ needs.metadata.outputs.stable_version }}-win-x64-setup.exe.blockmap"
|
||||||
|
$checksumFile = "$versionedInstaller.sha256"
|
||||||
|
Copy-Item $sourceInstaller (Join-Path $releaseDir $versionedInstaller)
|
||||||
|
Copy-Item $sourceBlockmap (Join-Path $releaseDir $versionedBlockmap)
|
||||||
|
|
||||||
|
$installerPath = Join-Path $releaseDir $versionedInstaller
|
||||||
|
$hash = (Get-FileHash -Path $installerPath -Algorithm SHA256).Hash.ToLowerInvariant()
|
||||||
|
"$hash $versionedInstaller" | Set-Content -Path (Join-Path $releaseDir $checksumFile)
|
||||||
|
$installerBytes = [System.IO.File]::ReadAllBytes($installerPath)
|
||||||
|
$installerSha512 = [System.Convert]::ToBase64String([System.Security.Cryptography.SHA512]::Create().ComputeHash($installerBytes))
|
||||||
|
$installerSize = (Get-Item $installerPath).Length
|
||||||
|
$installerUrl = "https://github.com/$env:GITHUB_REPOSITORY/releases/download/${{ needs.metadata.outputs.version_tag }}/$versionedInstaller"
|
||||||
|
$releaseDate = [DateTime]::UtcNow.ToString("yyyy-MM-ddTHH:mm:ssZ")
|
||||||
|
@(
|
||||||
|
'version: "${{ needs.metadata.outputs.stable_version }}"'
|
||||||
|
'files:'
|
||||||
|
" - url: `"$installerUrl`""
|
||||||
|
" sha512: `"$installerSha512`""
|
||||||
|
" size: $installerSize"
|
||||||
|
"path: `"$installerUrl`""
|
||||||
|
"sha512: `"$installerSha512`""
|
||||||
|
"releaseDate: `"$releaseDate`""
|
||||||
|
"releaseNotes: `"Open Design ${{ needs.metadata.outputs.stable_version }}`""
|
||||||
|
) | Set-Content -Path (Join-Path $releaseDir "latest.yml")
|
||||||
|
|
||||||
|
- name: Upload windows release bundle
|
||||||
|
uses: actions/upload-artifact@v7
|
||||||
|
with:
|
||||||
|
name: open-design-stable-win-release-assets
|
||||||
|
path: ${{ runner.temp }}/release-assets
|
||||||
|
|
||||||
|
build_linux:
|
||||||
|
name: Build stable linux x64
|
||||||
|
needs: [metadata, verify]
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ github.token }}
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v6.0.2
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- 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
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
|
# `--containerized` builds the AppImage inside the electronuserland/builder
|
||||||
|
# Docker image (glibc 2.27 baseline) so the resulting binary runs on older
|
||||||
|
# distros than ubuntu-latest's glibc 2.39. Docker is preinstalled on the
|
||||||
|
# GitHub-hosted ubuntu-latest runner, so no extra setup is required.
|
||||||
|
- name: Build stable linux artifacts
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
pnpm exec tools-pack linux build \
|
||||||
|
--dir "$RUNNER_TEMP/tools-pack" \
|
||||||
|
--namespace release-stable-linux \
|
||||||
|
--portable \
|
||||||
|
--to appimage \
|
||||||
|
--containerized \
|
||||||
|
--json
|
||||||
|
|
||||||
|
- name: Prepare linux stable assets
|
||||||
|
env:
|
||||||
|
STABLE_VERSION: ${{ needs.metadata.outputs.stable_version }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
release_dir="$RUNNER_TEMP/release-assets"
|
||||||
|
mkdir -p "$release_dir"
|
||||||
|
|
||||||
|
source_appimage="$RUNNER_TEMP/tools-pack/out/linux/namespaces/release-stable-linux/builder/Open Design-release-stable-linux.AppImage"
|
||||||
|
if [ ! -f "$source_appimage" ]; then
|
||||||
|
echo "expected AppImage not found at $source_appimage" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Linux currently has no signing path in tools-pack; the asset has no
|
||||||
|
# signing-related suffix (matches the windows convention above).
|
||||||
|
versioned_appimage="open-design-${STABLE_VERSION}-linux-x64.AppImage"
|
||||||
|
checksum_file="$versioned_appimage.sha256"
|
||||||
|
|
||||||
|
cp "$source_appimage" "$release_dir/$versioned_appimage"
|
||||||
|
(
|
||||||
|
cd "$release_dir"
|
||||||
|
sha256sum "$versioned_appimage" > "$checksum_file"
|
||||||
|
)
|
||||||
|
|
||||||
|
- name: Upload linux release bundle
|
||||||
|
uses: actions/upload-artifact@v7
|
||||||
|
with:
|
||||||
|
name: open-design-stable-linux-release-assets
|
||||||
|
path: ${{ runner.temp }}/release-assets
|
||||||
|
|
||||||
|
publish:
|
||||||
|
name: Publish stable release
|
||||||
|
needs:
|
||||||
|
- metadata
|
||||||
|
- verify
|
||||||
|
- build_mac
|
||||||
|
- build_win
|
||||||
|
- build_linux
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ github.token }}
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v6.0.2
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Pre-flight tag/release check
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
if git ls-remote --exit-code --tags origin "refs/tags/${{ needs.metadata.outputs.version_tag }}" >/dev/null 2>&1; then
|
||||||
|
echo "tag ${{ needs.metadata.outputs.version_tag }} already exists on origin; aborting" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if gh release view "${{ needs.metadata.outputs.version_tag }}" >/dev/null 2>&1; then
|
||||||
|
echo "release ${{ needs.metadata.outputs.version_tag }} already exists; aborting" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Download mac release bundle
|
||||||
|
uses: actions/download-artifact@v8
|
||||||
|
with:
|
||||||
|
name: open-design-stable-mac-release-assets
|
||||||
|
path: ${{ runner.temp }}/release-assets/mac
|
||||||
|
|
||||||
|
- name: Download windows release bundle
|
||||||
|
uses: actions/download-artifact@v8
|
||||||
|
with:
|
||||||
|
name: open-design-stable-win-release-assets
|
||||||
|
path: ${{ runner.temp }}/release-assets/win
|
||||||
|
|
||||||
|
- name: Download linux release bundle
|
||||||
|
uses: actions/download-artifact@v8
|
||||||
|
with:
|
||||||
|
name: open-design-stable-linux-release-assets
|
||||||
|
path: ${{ runner.temp }}/release-assets/linux
|
||||||
|
|
||||||
|
- name: Write release notes shell
|
||||||
|
id: notes
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
notes_file="$RUNNER_TEMP/open-design-stable-notes.md"
|
||||||
|
cat > "$notes_file" <<EOF
|
||||||
|
## Summary
|
||||||
|
- channel: stable
|
||||||
|
- version: ${{ needs.metadata.outputs.stable_version }}
|
||||||
|
- mac signed/notarized: ${{ inputs.mac_signed }}
|
||||||
|
- windows signed: false
|
||||||
|
- branch: ${{ needs.metadata.outputs.branch }}
|
||||||
|
- commit: ${{ needs.metadata.outputs.commit }}
|
||||||
|
|
||||||
|
See [CHANGELOG.md](https://github.com/${GITHUB_REPOSITORY}/blob/${{ needs.metadata.outputs.version_tag }}/CHANGELOG.md) for the full release notes.
|
||||||
|
|
||||||
|
This stable release ships mac arm64 DMG/update ZIP, Windows x64 NSIS installer assets, Linux x64 AppImage (no auto-update yet), checksums, and updater feed files.
|
||||||
|
EOF
|
||||||
|
echo "notes_file=$notes_file" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
- name: Create draft release with tag
|
||||||
|
id: create_release
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
# gh release create creates the tag at $GITHUB_SHA atomically with the release.
|
||||||
|
# Using --draft keeps the release invisible until all assets upload successfully;
|
||||||
|
# the cleanup step rolls back the release + tag together if any subsequent step fails.
|
||||||
|
gh release create "${{ needs.metadata.outputs.version_tag }}" \
|
||||||
|
--target "$GITHUB_SHA" \
|
||||||
|
--title "${{ needs.metadata.outputs.release_name }}" \
|
||||||
|
--notes-file "${{ steps.notes.outputs.notes_file }}" \
|
||||||
|
--draft
|
||||||
|
|
||||||
|
- name: Upload assets to draft release
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
all_release_dir="$RUNNER_TEMP/release-assets/all"
|
||||||
|
mkdir -p "$all_release_dir"
|
||||||
|
cp "$RUNNER_TEMP/release-assets/mac"/* "$all_release_dir/"
|
||||||
|
cp "$RUNNER_TEMP/release-assets/win"/* "$all_release_dir/"
|
||||||
|
cp "$RUNNER_TEMP/release-assets/linux"/* "$all_release_dir/"
|
||||||
|
gh release upload "${{ needs.metadata.outputs.version_tag }}" "$all_release_dir"/*
|
||||||
|
|
||||||
|
- name: Promote draft to published latest
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
gh release edit "${{ needs.metadata.outputs.version_tag }}" \
|
||||||
|
--draft=false \
|
||||||
|
--latest
|
||||||
|
|
||||||
|
- name: Cleanup release + tag on failure
|
||||||
|
if: failure() && steps.create_release.outcome == 'success'
|
||||||
|
run: |
|
||||||
|
set +e
|
||||||
|
echo "publish failed after release was created; rolling back release and tag"
|
||||||
|
gh release delete "${{ needs.metadata.outputs.version_tag }}" --cleanup-tag --yes
|
||||||
|
# belt-and-suspenders: ensure remote tag is gone even if --cleanup-tag missed
|
||||||
|
git push origin --delete "refs/tags/${{ needs.metadata.outputs.version_tag }}" || true
|
||||||
|
|
||||||
|
- name: Publish summary
|
||||||
|
run: |
|
||||||
|
{
|
||||||
|
echo "## Stable release"
|
||||||
|
echo "- Channel: stable"
|
||||||
|
echo "- Version: ${{ needs.metadata.outputs.stable_version }}"
|
||||||
|
echo "- Version tag: ${{ needs.metadata.outputs.version_tag }}"
|
||||||
|
echo "- mac signed/notarized: ${{ inputs.mac_signed }}"
|
||||||
|
echo "- windows signed: false"
|
||||||
|
echo "- mac assets: open-design-${{ needs.metadata.outputs.stable_version }}-mac-arm64.dmg, open-design-${{ needs.metadata.outputs.stable_version }}-mac-arm64.zip"
|
||||||
|
echo "- win assets: open-design-${{ needs.metadata.outputs.stable_version }}-win-x64-setup.exe, open-design-${{ needs.metadata.outputs.stable_version }}-win-x64-setup.exe.blockmap"
|
||||||
|
echo "- linux assets: open-design-${{ needs.metadata.outputs.stable_version }}-linux-x64.AppImage"
|
||||||
|
echo "- Feeds: latest-mac.yml, latest.yml (no latest-linux.yml; AppImage updater not yet wired)"
|
||||||
|
} >> "$GITHUB_STEP_SUMMARY"
|
||||||
+4
-4
@@ -698,7 +698,7 @@ Vollständiger Walkthrough, Merge-Messlatte, Code Style und was wir nicht annehm
|
|||||||
Danke an alle, die Open Design vorangebracht haben: durch Code, Docs, Feedback, neue Skills, neue Design Systems oder auch ein scharfes Issue. Jeder echte Beitrag zählt, und die Wand unten ist die einfachste Art, das laut zu sagen.
|
Danke an alle, die Open Design vorangebracht haben: durch Code, Docs, Feedback, neue Skills, neue Design Systems oder auch ein scharfes Issue. Jeder echte Beitrag zählt, und die Wand unten ist die einfachste Art, das laut zu sagen.
|
||||||
|
|
||||||
<a href="https://github.com/nexu-io/open-design/graphs/contributors">
|
<a href="https://github.com/nexu-io/open-design/graphs/contributors">
|
||||||
<img src="https://contrib.rocks/image?repo=nexu-io/open-design&cache_bust=2026-05-04" alt="Open Design contributors" />
|
<img src="https://contrib.rocks/image?repo=nexu-io/open-design&cache_bust=2026-09-30" alt="Open Design contributors" />
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
Wenn Sie Ihren ersten PR gemergt haben: willkommen. Das Label [`good-first-issue`](https://github.com/nexu-io/open-design/labels/good-first-issue) ist der Einstiegspunkt.
|
Wenn Sie Ihren ersten PR gemergt haben: willkommen. Das Label [`good-first-issue`](https://github.com/nexu-io/open-design/labels/good-first-issue) ist der Einstiegspunkt.
|
||||||
@@ -715,9 +715,9 @@ Das SVG oben wird täglich von [`.github/workflows/metrics.yml`](.github/workflo
|
|||||||
|
|
||||||
<a href="https://star-history.com/#nexu-io/open-design&Date">
|
<a href="https://star-history.com/#nexu-io/open-design&Date">
|
||||||
<picture>
|
<picture>
|
||||||
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=nexu-io/open-design&type=Date&theme=dark&cache_bust=2026-05-04" />
|
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=nexu-io/open-design&type=Date&theme=dark&cache_bust=2026-09-30" />
|
||||||
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=nexu-io/open-design&type=Date&cache_bust=2026-05-04" />
|
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=nexu-io/open-design&type=Date&cache_bust=2026-09-30" />
|
||||||
<img alt="Open Design star history" src="https://api.star-history.com/svg?repos=nexu-io/open-design&type=Date&cache_bust=2026-05-04" />
|
<img alt="Open Design star history" src="https://api.star-history.com/svg?repos=nexu-io/open-design&type=Date&cache_bust=2026-09-30" />
|
||||||
</picture>
|
</picture>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
|||||||
+4
-4
@@ -708,7 +708,7 @@ Guide complet, critères de merge, style de code et refus fréquents → [`CONTR
|
|||||||
Merci à toutes les personnes qui font avancer Open Design : code, docs, retours, nouveaux Skills, nouveaux Design Systems ou issues bien ciblées. Chaque vraie contribution compte.
|
Merci à toutes les personnes qui font avancer Open Design : code, docs, retours, nouveaux Skills, nouveaux Design Systems ou issues bien ciblées. Chaque vraie contribution compte.
|
||||||
|
|
||||||
<a href="https://github.com/nexu-io/open-design/graphs/contributors">
|
<a href="https://github.com/nexu-io/open-design/graphs/contributors">
|
||||||
<img src="https://contrib.rocks/image?repo=nexu-io/open-design&cache_bust=2026-05-04" alt="Contributeurs Open Design" />
|
<img src="https://contrib.rocks/image?repo=nexu-io/open-design&cache_bust=2026-09-30" alt="Contributeurs Open Design" />
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
Si vous avez livré votre première PR, bienvenue. Le label [`good-first-issue`](https://github.com/nexu-io/open-design/labels/good-first-issue) est le point d’entrée.
|
Si vous avez livré votre première PR, bienvenue. Le label [`good-first-issue`](https://github.com/nexu-io/open-design/labels/good-first-issue) est le point d’entrée.
|
||||||
@@ -725,9 +725,9 @@ Le SVG ci-dessus est régénéré chaque jour par [`.github/workflows/metrics.ym
|
|||||||
|
|
||||||
<a href="https://star-history.com/#nexu-io/open-design&Date">
|
<a href="https://star-history.com/#nexu-io/open-design&Date">
|
||||||
<picture>
|
<picture>
|
||||||
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=nexu-io/open-design&type=Date&theme=dark&cache_bust=2026-05-04" />
|
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=nexu-io/open-design&type=Date&theme=dark&cache_bust=2026-09-30" />
|
||||||
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=nexu-io/open-design&type=Date&cache_bust=2026-05-04" />
|
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=nexu-io/open-design&type=Date&cache_bust=2026-09-30" />
|
||||||
<img alt="Historique des stars Open Design" src="https://api.star-history.com/svg?repos=nexu-io/open-design&type=Date&cache_bust=2026-05-04" />
|
<img alt="Historique des stars Open Design" src="https://api.star-history.com/svg?repos=nexu-io/open-design&type=Date&cache_bust=2026-09-30" />
|
||||||
</picture>
|
</picture>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
|||||||
+4
-4
@@ -695,7 +695,7 @@ Issue、PR、新 Skill、新 Design System を歓迎します。最も効果の
|
|||||||
コード、ドキュメント、フィードバック、新 Skill、新 Design System、あるいは鋭い Issue — あらゆる形で Open Design を前進させてくださったすべての方に感謝します。すべての実質的なコントリビューションは大切であり、以下のウォールは最もシンプルな感謝の表明です。
|
コード、ドキュメント、フィードバック、新 Skill、新 Design System、あるいは鋭い Issue — あらゆる形で Open Design を前進させてくださったすべての方に感謝します。すべての実質的なコントリビューションは大切であり、以下のウォールは最もシンプルな感謝の表明です。
|
||||||
|
|
||||||
<a href="https://github.com/nexu-io/open-design/graphs/contributors">
|
<a href="https://github.com/nexu-io/open-design/graphs/contributors">
|
||||||
<img src="https://contrib.rocks/image?repo=nexu-io/open-design&cache_bust=2026-05-04" alt="Open Design コントリビューター" />
|
<img src="https://contrib.rocks/image?repo=nexu-io/open-design&cache_bust=2026-09-30" alt="Open Design コントリビューター" />
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
初めての PR を送った方 — ようこそ。[`good-first-issue`](https://github.com/nexu-io/open-design/labels/good-first-issue) ラベルがエントリポイントです。
|
初めての PR を送った方 — ようこそ。[`good-first-issue`](https://github.com/nexu-io/open-design/labels/good-first-issue) ラベルがエントリポイントです。
|
||||||
@@ -712,9 +712,9 @@ Issue、PR、新 Skill、新 Design System を歓迎します。最も効果の
|
|||||||
|
|
||||||
<a href="https://star-history.com/#nexu-io/open-design&Date">
|
<a href="https://star-history.com/#nexu-io/open-design&Date">
|
||||||
<picture>
|
<picture>
|
||||||
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=nexu-io/open-design&type=Date&theme=dark&cache_bust=2026-05-04" />
|
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=nexu-io/open-design&type=Date&theme=dark&cache_bust=2026-09-30" />
|
||||||
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=nexu-io/open-design&type=Date&cache_bust=2026-05-04" />
|
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=nexu-io/open-design&type=Date&cache_bust=2026-09-30" />
|
||||||
<img alt="Open Design star history" src="https://api.star-history.com/svg?repos=nexu-io/open-design&type=Date&cache_bust=2026-05-04" />
|
<img alt="Open Design star history" src="https://api.star-history.com/svg?repos=nexu-io/open-design&type=Date&cache_bust=2026-09-30" />
|
||||||
</picture>
|
</picture>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
|||||||
+4
-4
@@ -696,7 +696,7 @@ daemon 부팅 시 `PATH`에서 자동 감지됩니다. 설정 필요 없음. 스
|
|||||||
Open Design을 앞으로 나아가게 도와준 모든 분께 감사드립니다 — 코드, 문서, 피드백, 새 skill, 새 디자인 시스템, 또는 날카로운 이슈 하나라도. 모든 진짜 기여가 의미 있고, 아래의 벽이 가장 직접적인 "감사합니다"입니다.
|
Open Design을 앞으로 나아가게 도와준 모든 분께 감사드립니다 — 코드, 문서, 피드백, 새 skill, 새 디자인 시스템, 또는 날카로운 이슈 하나라도. 모든 진짜 기여가 의미 있고, 아래의 벽이 가장 직접적인 "감사합니다"입니다.
|
||||||
|
|
||||||
<a href="https://github.com/nexu-io/open-design/graphs/contributors">
|
<a href="https://github.com/nexu-io/open-design/graphs/contributors">
|
||||||
<img src="https://contrib.rocks/image?repo=nexu-io/open-design&cache_bust=2026-05-04" alt="Open Design 컨트리뷰터" />
|
<img src="https://contrib.rocks/image?repo=nexu-io/open-design&cache_bust=2026-09-30" alt="Open Design 컨트리뷰터" />
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
첫 PR을 보냈다면 — 환영합니다. [`good-first-issue`](https://github.com/nexu-io/open-design/labels/good-first-issue) 레이블이 시작점입니다.
|
첫 PR을 보냈다면 — 환영합니다. [`good-first-issue`](https://github.com/nexu-io/open-design/labels/good-first-issue) 레이블이 시작점입니다.
|
||||||
@@ -713,9 +713,9 @@ Open Design을 앞으로 나아가게 도와준 모든 분께 감사드립니다
|
|||||||
|
|
||||||
<a href="https://star-history.com/#nexu-io/open-design&Date">
|
<a href="https://star-history.com/#nexu-io/open-design&Date">
|
||||||
<picture>
|
<picture>
|
||||||
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=nexu-io/open-design&type=Date&theme=dark&cache_bust=2026-05-04" />
|
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=nexu-io/open-design&type=Date&theme=dark&cache_bust=2026-09-30" />
|
||||||
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=nexu-io/open-design&type=Date&cache_bust=2026-05-04" />
|
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=nexu-io/open-design&type=Date&cache_bust=2026-09-30" />
|
||||||
<img alt="Open Design star history" src="https://api.star-history.com/svg?repos=nexu-io/open-design&type=Date&cache_bust=2026-05-04" />
|
<img alt="Open Design star history" src="https://api.star-history.com/svg?repos=nexu-io/open-design&type=Date&cache_bust=2026-09-30" />
|
||||||
</picture>
|
</picture>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
|||||||
@@ -716,7 +716,7 @@ Full walkthrough, bar-for-merging, code style, and what we don't accept → [`CO
|
|||||||
Thanks to everyone who has helped move Open Design forward — through code, docs, feedback, new skills, new design systems, or even a sharp issue. Every real contribution counts, and the wall below is the easiest way to say so out loud.
|
Thanks to everyone who has helped move Open Design forward — through code, docs, feedback, new skills, new design systems, or even a sharp issue. Every real contribution counts, and the wall below is the easiest way to say so out loud.
|
||||||
|
|
||||||
<a href="https://github.com/nexu-io/open-design/graphs/contributors">
|
<a href="https://github.com/nexu-io/open-design/graphs/contributors">
|
||||||
<img src="https://contrib.rocks/image?repo=nexu-io/open-design&cache_bust=2026-05-04" alt="Open Design contributors" />
|
<img src="https://contrib.rocks/image?repo=nexu-io/open-design&cache_bust=2026-09-30" alt="Open Design contributors" />
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
If you've shipped your first PR — welcome. The [`good-first-issue`](https://github.com/nexu-io/open-design/labels/good-first-issue) label is the entry point.
|
If you've shipped your first PR — welcome. The [`good-first-issue`](https://github.com/nexu-io/open-design/labels/good-first-issue) label is the entry point.
|
||||||
@@ -733,9 +733,9 @@ The SVG above is regenerated daily by [`.github/workflows/metrics.yml`](.github/
|
|||||||
|
|
||||||
<a href="https://star-history.com/#nexu-io/open-design&Date">
|
<a href="https://star-history.com/#nexu-io/open-design&Date">
|
||||||
<picture>
|
<picture>
|
||||||
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=nexu-io/open-design&type=Date&theme=dark&cache_bust=2026-05-04" />
|
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=nexu-io/open-design&type=Date&theme=dark&cache_bust=2026-09-30" />
|
||||||
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=nexu-io/open-design&type=Date&cache_bust=2026-05-04" />
|
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=nexu-io/open-design&type=Date&cache_bust=2026-09-30" />
|
||||||
<img alt="Open Design star history" src="https://api.star-history.com/svg?repos=nexu-io/open-design&type=Date&cache_bust=2026-05-04" />
|
<img alt="Open Design star history" src="https://api.star-history.com/svg?repos=nexu-io/open-design&type=Date&cache_bust=2026-09-30" />
|
||||||
</picture>
|
</picture>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
|||||||
+4
-4
@@ -704,7 +704,7 @@ Issues, PR, новые skills и новые design systems приветству
|
|||||||
Спасибо всем, кто помогает двигать Open Design вперёд — кодом, документацией, обратной связью, новыми skills, новыми design systems или просто точным issue. Вклад любого реального масштаба здесь важен, а стена ниже — самый простой способ сказать это вслух.
|
Спасибо всем, кто помогает двигать Open Design вперёд — кодом, документацией, обратной связью, новыми skills, новыми design systems или просто точным issue. Вклад любого реального масштаба здесь важен, а стена ниже — самый простой способ сказать это вслух.
|
||||||
|
|
||||||
<a href="https://github.com/nexu-io/open-design/graphs/contributors">
|
<a href="https://github.com/nexu-io/open-design/graphs/contributors">
|
||||||
<img src="https://contrib.rocks/image?repo=nexu-io/open-design&cache_bust=2026-05-04" alt="Contributors Open Design" />
|
<img src="https://contrib.rocks/image?repo=nexu-io/open-design&cache_bust=2026-09-30" alt="Contributors Open Design" />
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
Если вы только что отправили свой первый PR — добро пожаловать. Метка [`good-first-issue`](https://github.com/nexu-io/open-design/labels/good-first-issue) — хорошая точка входа.
|
Если вы только что отправили свой первый PR — добро пожаловать. Метка [`good-first-issue`](https://github.com/nexu-io/open-design/labels/good-first-issue) — хорошая точка входа.
|
||||||
@@ -721,9 +721,9 @@ SVG выше ежедневно пересобирается workflow [`.github/
|
|||||||
|
|
||||||
<a href="https://star-history.com/#nexu-io/open-design&Date">
|
<a href="https://star-history.com/#nexu-io/open-design&Date">
|
||||||
<picture>
|
<picture>
|
||||||
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=nexu-io/open-design&type=Date&theme=dark&cache_bust=2026-05-04" />
|
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=nexu-io/open-design&type=Date&theme=dark&cache_bust=2026-09-30" />
|
||||||
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=nexu-io/open-design&type=Date&cache_bust=2026-05-04" />
|
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=nexu-io/open-design&type=Date&cache_bust=2026-09-30" />
|
||||||
<img alt="История звёзд Open Design" src="https://api.star-history.com/svg?repos=nexu-io/open-design&type=Date&cache_bust=2026-05-04" />
|
<img alt="История звёзд Open Design" src="https://api.star-history.com/svg?repos=nexu-io/open-design&type=Date&cache_bust=2026-09-30" />
|
||||||
</picture>
|
</picture>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
|||||||
+4
-4
@@ -704,7 +704,7 @@ OD не зупиняється на коді. Та сама поверхня ч
|
|||||||
Дякуємо всім, хто допоміг просувати Open Design — через код, документацію, зворотний зв'язок, нові навички, нові системи дизайну або навіть гостре питання. Кожен реальний внесок рахується, а стіна нижче — найпростіший спосіб сказати це вголос.
|
Дякуємо всім, хто допоміг просувати Open Design — через код, документацію, зворотний зв'язок, нові навички, нові системи дизайну або навіть гостре питання. Кожен реальний внесок рахується, а стіна нижче — найпростіший спосіб сказати це вголос.
|
||||||
|
|
||||||
<a href="https://github.com/nexu-io/open-design/graphs/contributors">
|
<a href="https://github.com/nexu-io/open-design/graphs/contributors">
|
||||||
<img src="https://contrib.rocks/image?repo=nexu-io/open-design&cache_bust=2026-05-04" alt="Контриб'ютори Open Design" />
|
<img src="https://contrib.rocks/image?repo=nexu-io/open-design&cache_bust=2026-09-30" alt="Контриб'ютори Open Design" />
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
Якщо ви злили свій перший PR — ласкаво просимо. Мітка [`good-first-issue`](https://github.com/nexu-io/open-design/labels/good-first-issue) — це точка входу.
|
Якщо ви злили свій перший PR — ласкаво просимо. Мітка [`good-first-issue`](https://github.com/nexu-io/open-design/labels/good-first-issue) — це точка входу.
|
||||||
@@ -721,9 +721,9 @@ SVG вище перегенерується щодня [`.github/workflows/metri
|
|||||||
|
|
||||||
<a href="https://star-history.com/#nexu-io/open-design&Date">
|
<a href="https://star-history.com/#nexu-io/open-design&Date">
|
||||||
<picture>
|
<picture>
|
||||||
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=nexu-io/open-design&type=Date&theme=dark&cache_bust=2026-05-04" />
|
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=nexu-io/open-design&type=Date&theme=dark&cache_bust=2026-09-30" />
|
||||||
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=nexu-io/open-design&type=Date&cache_bust=2026-05-04" />
|
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=nexu-io/open-design&type=Date&cache_bust=2026-09-30" />
|
||||||
<img alt="Історія зірок Open Design" src="https://api.star-history.com/svg?repos=nexu-io/open-design&type=Date&cache_bust=2026-05-04" />
|
<img alt="Історія зірок Open Design" src="https://api.star-history.com/svg?repos=nexu-io/open-design&type=Date&cache_bust=2026-09-30" />
|
||||||
</picture>
|
</picture>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
|||||||
+4
-4
@@ -694,7 +694,7 @@ Daemon 启动时从 `PATH` 自动检测,无需配置。流式分发逻辑在 [
|
|||||||
感谢每一位让 Open Design 变得更好的朋友 —— 无论是写代码、修文档、提 issue、加 skill 还是加 design system,每一次真实贡献都会被记住。下面这面墙是最直观的「Thank you」。
|
感谢每一位让 Open Design 变得更好的朋友 —— 无论是写代码、修文档、提 issue、加 skill 还是加 design system,每一次真实贡献都会被记住。下面这面墙是最直观的「Thank you」。
|
||||||
|
|
||||||
<a href="https://github.com/nexu-io/open-design/graphs/contributors">
|
<a href="https://github.com/nexu-io/open-design/graphs/contributors">
|
||||||
<img src="https://contrib.rocks/image?repo=nexu-io/open-design&cache_bust=2026-05-04" alt="Open Design 贡献者" />
|
<img src="https://contrib.rocks/image?repo=nexu-io/open-design&cache_bust=2026-09-30" alt="Open Design 贡献者" />
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
第一次提 PR?欢迎从 [`good-first-issue`](https://github.com/nexu-io/open-design/labels/good-first-issue) 标签起步。
|
第一次提 PR?欢迎从 [`good-first-issue`](https://github.com/nexu-io/open-design/labels/good-first-issue) 标签起步。
|
||||||
@@ -711,9 +711,9 @@ Daemon 启动时从 `PATH` 自动检测,无需配置。流式分发逻辑在 [
|
|||||||
|
|
||||||
<a href="https://star-history.com/#nexu-io/open-design&Date">
|
<a href="https://star-history.com/#nexu-io/open-design&Date">
|
||||||
<picture>
|
<picture>
|
||||||
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=nexu-io/open-design&type=Date&theme=dark&cache_bust=2026-05-04" />
|
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=nexu-io/open-design&type=Date&theme=dark&cache_bust=2026-09-30" />
|
||||||
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=nexu-io/open-design&type=Date&cache_bust=2026-05-04" />
|
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=nexu-io/open-design&type=Date&cache_bust=2026-09-30" />
|
||||||
<img alt="Open Design star history" src="https://api.star-history.com/svg?repos=nexu-io/open-design&type=Date&cache_bust=2026-05-04" />
|
<img alt="Open Design star history" src="https://api.star-history.com/svg?repos=nexu-io/open-design&type=Date&cache_bust=2026-09-30" />
|
||||||
</picture>
|
</picture>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
|||||||
+4
-4
@@ -620,7 +620,7 @@ Daemon 啟動時從 `PATH` 自動檢測,無需配置。流式分發邏輯在 [
|
|||||||
感謝每一位讓 Open Design 變得更好的朋友 —— 無論是寫程式碼、修文檔、提 issue、加 skill 還是加 design system,每一次真實貢獻都會被記住。下面這面牆是最直觀的「Thank you」。
|
感謝每一位讓 Open Design 變得更好的朋友 —— 無論是寫程式碼、修文檔、提 issue、加 skill 還是加 design system,每一次真實貢獻都會被記住。下面這面牆是最直觀的「Thank you」。
|
||||||
|
|
||||||
<a href="https://github.com/nexu-io/open-design/graphs/contributors">
|
<a href="https://github.com/nexu-io/open-design/graphs/contributors">
|
||||||
<img src="https://contrib.rocks/image?repo=nexu-io/open-design&cache_bust=2026-05-04" alt="Open Design 貢獻者" />
|
<img src="https://contrib.rocks/image?repo=nexu-io/open-design&cache_bust=2026-09-30" alt="Open Design 貢獻者" />
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
第一次提 PR?歡迎從 [`good-first-issue`](https://github.com/nexu-io/open-design/labels/good-first-issue) 標籤起步。
|
第一次提 PR?歡迎從 [`good-first-issue`](https://github.com/nexu-io/open-design/labels/good-first-issue) 標籤起步。
|
||||||
@@ -637,9 +637,9 @@ Daemon 啟動時從 `PATH` 自動檢測,無需配置。流式分發邏輯在 [
|
|||||||
|
|
||||||
<a href="https://star-history.com/#nexu-io/open-design&Date">
|
<a href="https://star-history.com/#nexu-io/open-design&Date">
|
||||||
<picture>
|
<picture>
|
||||||
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=nexu-io/open-design&type=Date&theme=dark&cache_bust=2026-05-04" />
|
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=nexu-io/open-design&type=Date&theme=dark&cache_bust=2026-09-30" />
|
||||||
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=nexu-io/open-design&type=Date&cache_bust=2026-05-04" />
|
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=nexu-io/open-design&type=Date&cache_bust=2026-09-30" />
|
||||||
<img alt="Open Design star history" src="https://api.star-history.com/svg?repos=nexu-io/open-design&type=Date&cache_bust=2026-05-04" />
|
<img alt="Open Design star history" src="https://api.star-history.com/svg?repos=nexu-io/open-design&type=Date&cache_bust=2026-09-30" />
|
||||||
</picture>
|
</picture>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user