Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
f1359f4
docs: design personal gallery architecture
lurui1997 Jul 21, 2026
e0a0371
docs: clarify gallery source and content rules
lurui1997 Jul 21, 2026
5682ff8
docs: plan personal gallery implementation
lurui1997 Jul 21, 2026
3dc4316
chore: preserve prose under notes
lurui1997 Jul 21, 2026
7f56773
build: establish gallery management toolchain
lurui1997 Jul 21, 2026
e37027e
fix: treat repository tools as esm
lurui1997 Jul 21, 2026
d3ea61e
feat: define strict project metadata contract
lurui1997 Jul 21, 2026
bda9a1b
fix: align metadata URL validation
lurui1997 Jul 21, 2026
ab32557
fix: expose project schema AJV extensions
lurui1997 Jul 21, 2026
88c8ce7
feat: validate project repository invariants
lurui1997 Jul 21, 2026
cedf706
fix: compose repository validation primitive
lurui1997 Jul 21, 2026
6f9a518
fix: harden project validation snapshots
lurui1997 Jul 21, 2026
7074469
feat: generate deterministic gallery project data
lurui1997 Jul 21, 2026
6d7c7b3
fix: skip empty generated readme excerpts
lurui1997 Jul 21, 2026
7b59e25
fix: publish verified immutable cover versions
lurui1997 Jul 21, 2026
1306caf
fix: recover abandoned asset staging
lurui1997 Jul 21, 2026
765a0f3
feat: scaffold projects from safe templates
lurui1997 Jul 21, 2026
64dc754
test: gate full project creation race
lurui1997 Jul 21, 2026
8080194
fix: publish projects without replacement
lurui1997 Jul 21, 2026
a9de121
docs: require atomic project publication
lurui1997 Jul 21, 2026
d4c07e8
docs: detail native publication helper
lurui1997 Jul 21, 2026
0ae186c
test: harden native publication adapter
lurui1997 Jul 21, 2026
43ffca3
fix: bound native helper diagnostics
lurui1997 Jul 21, 2026
50f7f72
feat: list gallery projects for maintenance
lurui1997 Jul 21, 2026
c391127
fix: harden gallery project listing
lurui1997 Jul 21, 2026
160a56c
feat: render static gallery and project routes
lurui1997 Jul 21, 2026
da2550e
feat: add shareable accessible project filters
lurui1997 Jul 21, 2026
1bab081
fix: harden accessible project filters
lurui1997 Jul 21, 2026
3168065
fix: preserve filter navigation state
lurui1997 Jul 21, 2026
ffbe42c
feat: style an accessible responsive project archive
lurui1997 Jul 21, 2026
d94b76f
fix: enforce accessible archive hit areas
lurui1997 Jul 21, 2026
5f0541b
docs: explain project and gallery maintenance
lurui1997 Jul 21, 2026
a95b7db
docs: align metadata example with specification
lurui1997 Jul 21, 2026
1123e19
fix: derive gallery base from repository config
lurui1997 Jul 21, 2026
ae63f1d
docs: clarify gallery project isolation
lurui1997 Jul 21, 2026
4eac44e
ci: validate and deploy gallery to GitHub Pages
lurui1997 Jul 21, 2026
ea6a84c
test: isolate gallery fixtures from deployment env
lurui1997 Jul 21, 2026
577f781
test: restore gallery deployment environment
lurui1997 Jul 21, 2026
658a149
ci: run checks on every push and pin Pages actions
lurui1997 Jul 21, 2026
804c212
fix: generate gallery index before CI tests
lurui1997 Jul 21, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions .github/workflows/pages.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
name: Gallery

on:
pull_request:
push:
workflow_dispatch:

permissions:
contents: read

concurrency:
group: pages-${{ github.ref }}
cancel-in-progress: true

jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
- uses: pnpm/action-setup@f40ffcd9367d9f12939873eb1018b921a783ffaa # v4
with:
version: 10.13.1
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: 22
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm check
env:
FAUST_GITHUB_OWNER: ${{ github.repository_owner }}
FAUST_GITHUB_REPOSITORY: ${{ github.event.repository.name }}
FAUST_GITHUB_BRANCH: ${{ github.event.repository.default_branch }}
FAUST_SITE: https://${{ github.repository_owner }}.github.io
FAUST_BASE: /${{ github.event.repository.name }}
- if: github.event_name == 'push' && github.ref_name == github.event.repository.default_branch
uses: actions/configure-pages@983d7736d9b0ae728b81ab479565c72886d7745b # v5
- if: github.event_name == 'push' && github.ref_name == github.event.repository.default_branch
uses: actions/upload-pages-artifact@56afc609e74202658d3ffba0e8f6dda462b719fa # v3
with:
path: gallery/dist

deploy:
if: github.event_name == 'push' && github.ref_name == github.event.repository.default_branch
needs: check
runs-on: ubuntu-latest
permissions:
contents: read
pages: write
id-token: write
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4
9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
node_modules/
gallery/node_modules/
gallery/.astro/
gallery/dist/
gallery/src/data/projects.generated.json
gallery/public/.project-assets-*
gallery/public/project-assets
coverage/
.DS_Store
2 changes: 2 additions & 0 deletions .npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
save-exact=true
strict-peer-dependencies=true
69 changes: 68 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,68 @@
# faust
# Faust

Faust 是一个“一个仓库、多种技术栈”的个人项目陈列馆。每个项目独立生活在 `projects/`;画廊工具只读取其元数据、README 和元数据显式引用的封面文件,不会执行项目代码或安装项目依赖,也不会构建或测试项目。

## 仓库结构

```text
faust/
├── gallery/ # Astro 静态站点
│ ├── public/ # 生成的封面版本与 project-assets 符号链接(不提交)
│ └── src/data/
│ └── projects.generated.json # 生成的画廊索引(不提交)
├── projects/ # 项目;首次创建前可以不存在
│ └── <slug>/
│ ├── project.json # 画廊元数据
│ ├── README.md # 项目说明与独立开发命令
│ └── ... # 项目自己的源码、依赖和工具链
├── templates/ # blank、web、script 创建模板
├── tools/ # 创建、校验、索引和列表工具
├── notes/ # 笔记;不进入画廊
├── docs/ # 元数据与维护文档
├── project.schema.json # 元数据契约的唯一权威来源
├── package.json # 根管理命令
└── pnpm-lock.yaml
```

## 从全新检出开始

要求:Linux 或 macOS、Node.js 22、Corepack,以及 pnpm 10.13.1(由 `packageManager` 固定)。

```sh
corepack enable
corepack prepare pnpm@10.13.1 --activate
pnpm install --frozen-lockfile
pnpm dev
```

`pnpm create:project` 第一次成功创建项目时会从已提交的 C 源码编译一个私有的原子发布助手,因此 `/usr/bin/cc` 必须存在:

- macOS:运行 `xcode-select --install` 安装 Xcode Command Line Tools。
- Linux:用系统包管理器安装 C 编译器,并确认 `/usr/bin/cc` 可执行(例如 Debian/Ubuntu 的 `build-essential`)。

Windows 等其他平台会明确报 `unsupported`;请在 Linux/macOS(本机、容器或 CI)中执行创建命令。仓库不提交任何原生二进制;助手按源码、平台和架构缓存在系统临时目录。

## 稳定命令

| 命令 | 作用 |
| --- | --- |
| `pnpm validate` | 校验全部项目目录、元数据、README 和封面 |
| `pnpm generate` | 校验后生成画廊索引和封面资源 |
| `pnpm dev` | 校验、生成并启动 Astro 开发服务器 |
| `pnpm build` | 校验、生成、类型检查并构建 `gallery/dist/` |
| `pnpm test` | 运行自动化测试 |
| `pnpm check` | 依次运行校验、测试和生产构建 |
| `pnpm create:project` | 交互式创建并原子发布一个项目 |
| `pnpm list:projects` | 按更新时间列出项目;空库会提示创建命令 |

所有根命令都应从仓库根目录运行。项目自己的安装、开发和测试命令写在该项目的 README 中,并在 `projects/<slug>` 内运行;项目不是 pnpm workspace 成员,其依赖不会被根 `pnpm install` 安装,也不会被根 `build` 或 `check` 隐式执行。

## 项目生命周期

1. 运行 `pnpm create:project`,填写标题、类型、模板与摘要并确认 slug。创建器写入安全默认值:`idea`、空标签、`featured: false`、无 demo/封面、本仓库源码链接。
2. 进入输出的 `projects/<slug>` 路径,按项目 README 独立开发。
3. 有实质变化时编辑 `project.json`:同步更新 `updatedAt`,按进度使用 `idea` → `building` → `shipped`;不再默认展示时设为 `archived`。状态可以按实际情况回退或恢复。
4. 若添加封面,把普通文件放在项目目录内并设置安全的相对 `cover` 路径;若部署演示,设置绝对 HTTP(S) `demo`。
5. 运行 `pnpm validate` 和 `pnpm list:projects`,提交前运行 `pnpm check`。不要手改或提交 `projects.generated.json`、`gallery/public/project-assets`、`.project-assets-*` 或 `gallery/dist/`。

完整字段与合法 JSON 示例见 [项目元数据](docs/project-metadata.md),开发、发布、恢复与模板维护见 [画廊维护](docs/gallery-maintenance.md)。
86 changes: 86 additions & 0 deletions docs/gallery-maintenance.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# 画廊维护 / Gallery maintenance

本文面向仓库 owner 和贡献者。根工具只读取项目元数据、README 和元数据显式引用的封面普通文件;它不会执行 `projects/*` 中的代码、安装项目依赖或运行项目自己的构建与测试。

## 日常开发与空画廊

```sh
pnpm dev
```

该命令先执行 `validate`、再生成索引与封面、最后启动 Astro。没有 `projects/` 或目录为空是合法状态:`pnpm validate` 输出 `Validated 0 projects`,`pnpm list:projects` 输出 `No projects found. Run pnpm create:project.`,站点显示如何创建首个项目。不要为消除空状态而提交占位项目。

校验失败会停止生成、开发和构建。诊断格式为:

```text
demo-project: cover — cover "missing.webp" is missing or unreadable; fix: add the referenced cover inside the project directory or set cover to null
```

按 `project`、`field` 和 `fix` 修复;常见原因包括缺少/非普通文件的 `project.json` 或 `README.md`、畸形 JSON、Schema 字段错误、slug 与目录不符、重复 slug、封面缺失或越界。只校验单个目录是内部 API 能力,不是稳定 CLI;维护者应运行完整的 `pnpm validate`。

## 索引和封面的生成与恢复

`pnpm generate` 先校验全部项目,按 `updatedAt` 降序、同日按 slug 升序写入 `gallery/src/data/projects.generated.json`。索引以临时文件加 rename 原子替换。

封面被复制到内容哈希命名的不可变目录 `gallery/public/.project-assets-<hash>/`,公开路径 `gallery/public/project-assets` 是指向当前版本的符号链接。生成器先完整校验 staging,再原子切换链接与索引;失败时恢复旧链接并清理本次版本,因此不要把普通目录或自己的文件放在这些保留路径中。

进程中断后直接重新运行 `pnpm generate`。它会清除自己遗留的 `.project-assets-stage-XXXXXX` 和锁名,复用完整的哈希版本,并在成功发布后尽力删除旧版本。若报活动版本不完整,不要手改索引:先确认项目封面可读,再删除**诊断点名且位于 `gallery/public/` 的损坏生成版本和 `project-assets` 链接**,重新生成。所有这些路径均在 `.gitignore` 中,不应提交。

## GitHub Pages 配置

默认发布身份来自 `tools/config.ts`:owner `lurui1997`、仓库 `faust`、分支 `main`,站点 `https://lurui1997.github.io`,base `/faust`。未设置 `FAUST_BASE` 时,工具与 Astro 都从 `FAUST_GITHUB_REPOSITORY` 派生 `/<仓库名>`,确保生成链接与构建路径一致。分叉、重命名或预览构建时可设置:

| 环境变量 | 作用 |
| --- | --- |
| `FAUST_GITHUB_OWNER` | `repository: "./"` 的 GitHub owner |
| `FAUST_GITHUB_REPOSITORY` | GitHub 仓库名及默认 Pages base |
| `FAUST_GITHUB_BRANCH` | 本仓库源码链接使用的分支 |
| `FAUST_SITE` | Astro 的绝对站点 origin |
| `FAUST_BASE` | 部署子路径,如 `/preview`;根部署可用 `/` |

例如:

```sh
FAUST_GITHUB_OWNER=acme FAUST_GITHUB_REPOSITORY=lab \
FAUST_GITHUB_BRANCH=main FAUST_SITE=https://acme.github.io FAUST_BASE=/lab \
pnpm build
```

环境变量必须同时提供给 `generate` 与 Astro 构建;使用 `pnpm build` 可保证二者处于同一进程环境,避免详情、封面和源码链接不一致。产物位于 `gallery/dist/`。

## 发布前人工可访问性检查

在 `pnpm check` 通过后,至少覆盖下表。使用真实浏览器测试宽屏和窄屏,并分别检查有/无项目、有/无封面与 demo。

| 场景 | 检查点 |
| --- | --- |
| 仅键盘 | Tab 顺序自然;skip link、筛选器、重置和项目链接可达;焦点清晰;重置后焦点回到索引标题 |
| 屏幕阅读器 | landmarks/标题层级合理;筛选标签明确;数量更新由 live region 宣告;封面 fallback 与链接有可理解名称 |
| 200%/窄屏 | 无横向溢出;长标题、标签和 URL 换行;控件和操作保持可用 |
| 对比度/焦点 | 正文、弱化文字、边框和焦点指示在实际背景上可辨 |
| 动效 | `prefers-reduced-motion: reduce` 下无非必要动画 |
| 无 JavaScript | 默认 active 项目仍可读,`noscript` 说明存在;归档项目可由直接详情链接访问 |
| 筛选状态 | type/status 查询参数可分享;无匹配与真正空库提示不同;Reset 恢复默认 active |
| 链接与资源 | Pages base 下首页、详情、封面、Demo 和 source 均正确;外链不产生脚本执行 |

## 扩展项目模板

新增模板只为减少重复起步工作,不得把项目接入根 workspace 或根构建:

1. 在 `templates/<name>/` 添加普通目录和以 `.tpl` 结尾的模板文件;禁止符号链接。目录和文件须归当前用户所有,且不可被 group/other 写入。
2. 仅使用创建器已提供的显式占位符;未知、畸形或残留的 `{{...}}` 会失败。模板不执行代码。
3. 每个模板生成 `README.md`,清楚写 Purpose、Development、Status;项目依赖和命令留在项目内。
4. 在 `TemplateName`、交互选项、开发提示和下一步提示中显式登记模板,并补充创建/安全测试。
5. 运行 `pnpm test` 和 `pnpm check`。不要把新项目设为 pnpm workspace 成员,也不要从根脚本代理其构建。

## 原生发布助手排障

创建器在最终发布前调用 `tools/native/rename-noreplace.c` 编译出的助手,保证并发创建不会覆盖已有目录。缓存为系统临时目录下当前用户专属的 `faust-native-<uid>/rename-noreplace-<hash>`,目录与文件必须归当前用户所有、不可被其他用户访问;源码/平台/架构变化会产生新 hash。仓库只提交 C 源码,不提交缓存二进制。

- `compiler is unavailable at /usr/bin/cc`:安装 Xcode CLT 或 Linux C 工具链,并确认该固定路径存在。
- `unsupported on <platform>`:改在 Linux/macOS 执行 `pnpm create:project`。
- `cache is not private and trusted` / `not a trusted restrictive regular file`:检查临时目录归属与权限;确认精确路径后删除该用户的 `faust-native-<uid>` 缓存,让下次创建以 `0700` 重建。不要删除整个系统临时目录。
- `kernel does not support atomic no-replace rename`:换用支持相应原子 rename 的 Linux/macOS 内核或环境;不要用普通 `mv` 绕过保护。
- `already exists or is being created`:这是安全冲突,不是缓存故障;选择新的 slug。

若编译诊断仍不清楚,保留完整终端输出、`node --version`、`pnpm --version`、操作系统/架构及 `/usr/bin/cc --version`,再报告工具问题。
95 changes: 95 additions & 0 deletions docs/project-metadata.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# 项目元数据 / Project metadata

每个 `projects/` 的直接子目录都必须含普通文件 `project.json` 和 `README.md`。`project.schema.json` 是字段、必填性与约束的唯一权威来源;本文是便于维护者阅读的说明。如二者不一致,以 Schema 为准并同步修正文档。

## 完整合法示例

目录名须为 `semantic-search`:

```json
{
"title": "Semantic Search Playground",
"slug": "semantic-search",
"summary": "用不同向量模型比较中文语义搜索效果。",
"status": "building",
"type": "ai",
"tags": ["embeddings", "search"],
"createdAt": "2026-07-21",
"updatedAt": "2026-07-21",
"featured": false,
"demo": null,
"repository": "./",
"cover": null
}
```

这 12 个字段全部必填,即使可空字段也必须写为 `null`。对象拒绝未知字段,拼错的键不会被静默忽略。

## 字段规则

| 字段 | 类型与规则 |
| --- | --- |
| `title` | 非空白字符串,1–100 字符 |
| `slug` | 1–64 字符;小写 ASCII kebab-case:`^[a-z0-9]+(?:-[a-z0-9]+)*$`;必须与目录名一致且在仓库内唯一 |
| `summary` | 非空白字符串,1–300 字符;公开展示的简述 |
| `status` | 下表中的一个状态 |
| `type` | 下表中的一个类型 |
| `tags` | 数组,最多 10 项且不可重复;每项 1–32 字符并遵循与 slug 相同的小写 kebab-case 形式;可为空数组 |
| `createdAt` | 真实 ISO 日历日期 `YYYY-MM-DD` |
| `updatedAt` | 真实 ISO 日历日期,且不得早于 `createdAt` |
| `featured` | 布尔值;用于视觉强调,不改变路由或分区 |
| `demo` | 无用户名/密码的绝对 `http://` 或 `https://` URL(最长 2048 字符),或 `null` |
| `repository` | 精确的 `"./"`,或无用户名/密码的绝对 HTTPS URL(最长 2048 字符) |
| `cover` | 安全、规范化的项目内相对路径(1–255 字符),或 `null`;还必须指向项目目录内存在的普通文件 |

`repository: "./"` 表示源码就在当前 Faust 仓库,画廊会按配置的 GitHub owner、仓库名、分支和 `projects/<slug>` 生成链接;它不是文件系统路径,也不要改成 `"."`。

安全封面路径不能以 `/` 开头,不能含反斜杠、冒号、查询串、片段、控制字符、空路径段、`.`/`..` 段或编码后的 `%2e`,不能以 `/` 结尾。例如 `assets/cover.webp` 合法,`../cover.png`、`assets//cover.png`、`C:\\cover.png`、`cover.png?raw=1` 均非法。符号链接或逃出项目目录的目标也不会通过仓库校验。

## 枚举

| `status` | 含义 |
| --- | --- |
| `idea` | 已记录,尚处于构想或起步阶段 |
| `building` | 正在实现或迭代 |
| `shipped` | 已形成可用成果 |
| `archived` | 已归档;默认画廊筛选不显示,但仍可查看 |

| `type` | 适用项目 |
| --- | --- |
| `web` | Web 页面或应用 |
| `service` | 服务或 API |
| `cli` | 命令行工具 |
| `ai` | AI/机器学习实验 |
| `script` | 脚本或自动化 |
| `other` | 以上均不适用 |

## 常见无效写法

以下片段都不是可提交的完整元数据:

```json
{ "slug": "Semantic_Search" }
```

含大写字母和下划线,也不会与 kebab-case 目录匹配。

```json
{ "createdAt": "2026-07-22", "updatedAt": "2026-07-21" }
```

更新时间早于创建日期。`2026-02-30` 这类不存在的日历日期也非法。

```json
{ "tags": ["ai", "ai", "中文"] }
```

标签重复,且 `中文` 不符合小写 ASCII kebab-case 规则。

```json
{ "repository": "http://example.com/source", "cover": "../outside.png", "extra": true }
```

外部源码只允许 HTTPS,封面发生目录穿越,`extra` 是未知字段。

运行 `pnpm validate` 可得到 `项目: 字段 — 原因; fix: 修复建议` 形式的诊断。外部 URL 只检查语法,不请求网络。
Loading
Loading