Compare commits
10
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
57cdbe1b5b | ||
|
|
60e18ded88 | ||
|
|
d72e9e02e3 | ||
|
|
d7d60cc34b | ||
|
|
661e80fade | ||
|
|
c74b2331bc | ||
|
|
eacc847c6a | ||
|
|
c80ada6af5 | ||
|
|
eab95953bf | ||
|
|
3ecac5b757 |
@@ -0,0 +1,9 @@
|
|||||||
|
# 阿里云 OSS 发布凭据(复制为 .env 后填真值,.env 已被 .gitignore 排除)
|
||||||
|
OSS_ACCESS_KEY_ID=
|
||||||
|
OSS_ACCESS_KEY_SECRET=
|
||||||
|
OSS_ENDPOINT=oss-cn-hangzhou.aliyuncs.com
|
||||||
|
OSS_BUCKET=official-site-base-demo
|
||||||
|
OSS_REGION=cn-hangzhou
|
||||||
|
|
||||||
|
# 本地 Gitea
|
||||||
|
GITEA_BASE_URL=http://localhost:3001
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
name: build-and-deploy
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
# raw checkout:绕开 actions/checkout(服务器拉 github action 不通)
|
||||||
|
# 仓库 public,job 容器经 official-site_default 网络访问 gitea:3000
|
||||||
|
- name: Checkout
|
||||||
|
run: |
|
||||||
|
git config --global --add safe.directory '*'
|
||||||
|
git clone "http://gitea:3000/${GITHUB_REPOSITORY}.git" .
|
||||||
|
git checkout "${GITHUB_SHA}"
|
||||||
|
|
||||||
|
- name: Install deps
|
||||||
|
run: npm ci --registry=https://registry.npmmirror.com
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: npm run build
|
||||||
|
|
||||||
|
- name: Verify dist
|
||||||
|
run: ls -la dist/
|
||||||
@@ -30,3 +30,7 @@ Thumbs.db
|
|||||||
.vscode/
|
.vscode/
|
||||||
.idea/
|
.idea/
|
||||||
*.swp
|
*.swp
|
||||||
|
|
||||||
|
# ===== 本地 Gitea/runner 数据(docker compose 产生)=====
|
||||||
|
docker/gitea-data/
|
||||||
|
docker/runner-data/
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import { defineConfig } from 'astro/config';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
site: 'https://example.com',
|
||||||
|
});
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
# 本地 Phase 0 验证用 Gitea + act_runner
|
||||||
|
# 用法:cd docker && docker compose -f gitea.docker-compose.yml up -d
|
||||||
|
# 说明:
|
||||||
|
# - Gitea Web: http://localhost:3001 (宿主 3001,避开 Astro dev 占用的 3000)
|
||||||
|
# - docker.sock 用单斜杠即可:compose 文件内 YAML 值不经 shell,
|
||||||
|
# Docker Desktop for Windows 下实测可正常访问宿主 daemon(已验证)
|
||||||
|
# - GITEA_RUNNER_REGISTRATION_TOKEN 在 gitea 与 runner 两端设相同值:
|
||||||
|
# gitea 启动时将其作为全局 registration token,runner 用它完成注册
|
||||||
|
services:
|
||||||
|
gitea:
|
||||||
|
image: gitea/gitea:1
|
||||||
|
environment:
|
||||||
|
- USER_UID=1000
|
||||||
|
- USER_GID=1000
|
||||||
|
- GITEA__security__INSTALL_LOCK=true # 跳过首装页面,以默认 SQLite 配置启动
|
||||||
|
- GITEA__server__ROOT_URL=http://localhost:3001/ # 对外访问地址(宿主端口)
|
||||||
|
- GITEA_RUNNER_REGISTRATION_TOKEN=phase0-local-actions-runner-token-0123456789abcdef0123456789 # 全局 registration token,与 runner 端一致
|
||||||
|
volumes:
|
||||||
|
- ./gitea-data:/data
|
||||||
|
ports:
|
||||||
|
- "3001:3000" # Web(宿主 3001,避开 Astro dev 占用的 3000)
|
||||||
|
- "2222:22" # SSH
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
runner:
|
||||||
|
image: gitea/act_runner:latest
|
||||||
|
environment:
|
||||||
|
- GITEA_INSTANCE_URL=http://gitea:3000
|
||||||
|
- GITEA_RUNNER_REGISTRATION_TOKEN=phase0-local-actions-runner-token-0123456789abcdef0123456789
|
||||||
|
- GITEA_RUNNER_LABELS=ubuntu-latest:docker://node:20-bookworm,ubuntu-24.04:docker://node:20-bookworm,ubuntu-22.04:docker://node:20-bookworm
|
||||||
|
volumes:
|
||||||
|
- ./runner-data:/data
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
|
depends_on:
|
||||||
|
- gitea
|
||||||
|
restart: unless-stopped
|
||||||
@@ -836,3 +836,25 @@ git push local main
|
|||||||
- Gitea OAuth PKCE 流是否开箱通(Task 6 Step 2 注明了降级方案)
|
- Gitea OAuth PKCE 流是否开箱通(Task 6 Step 2 注明了降级方案)
|
||||||
- `act_runner` 注册 token(compose 写死 `phase0-local-token`,仅本地)
|
- `act_runner` 注册 token(compose 写死 `phase0-local-token`,仅本地)
|
||||||
- unpkg CDN 国内访问速度(生产换自托管)
|
- unpkg CDN 国内访问速度(生产换自托管)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 执行记录(2026-07-23 实施偏差与现状)
|
||||||
|
|
||||||
|
代码侧 Task 1-4、7、8 + Task 5 文件部分已完成(`feat/phase0-tech-validation` 分支,7 commit)。
|
||||||
|
|
||||||
|
**实际依赖版本(偏离计划 `@latest`):**
|
||||||
|
|
||||||
|
| 包 | 计划 | 实际 | 原因 |
|
||||||
|
|----|------|------|------|
|
||||||
|
| astro | latest(7.1) | **5.18.2** | latest 要 node≥22.12,本机 node 22.11 被拒启动 |
|
||||||
|
| vitest | latest(4) | **3.2.7** | latest(4) 依赖 rolldown,Windows native binding 装不上 |
|
||||||
|
| zod | latest(4) | **3.25.76** | astro 5 内置 zod 3,zod 4 与 astro:content 不兼容 |
|
||||||
|
|
||||||
|
**其他调整:**
|
||||||
|
- Task 2 schema 改用独立 `zod` 包(`import { z } from 'zod'`)而非 `astro:content`,让 vitest 能直接测(`astro:content` 是虚拟模块);版本与 astro 内置 dedupe 为同一份 3.25.76。
|
||||||
|
- Sveltia CMS backends 已查实:原生支持 GitHub / GitLab / **Gitea**,**不支持 Gitee / Bitbucket**(故选自建 Gitea,见 spec §三)。
|
||||||
|
|
||||||
|
**验证已过:** schema 单测 4 个全过;`astro build` 出 3 页;`deploy-oss.sh` 空跑跳过退出 0。
|
||||||
|
|
||||||
|
**阻塞(待用户决策):** 本机无 Docker,Task 5(起 Gitea)/ 6(OAuth)/ 9(端到端)无法本地跑通。Gitea 部署方式三选一:① 本地装 Docker Desktop ② 本地 Gitea 二进制 ③ 上服务器(ops 机或新轻量云)。
|
||||||
|
|||||||
Generated
+6153
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"name": "official-site-base",
|
||||||
|
"type": "module",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"dev": "astro dev",
|
||||||
|
"build": "astro build",
|
||||||
|
"preview": "astro preview",
|
||||||
|
"test": "vitest run"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"astro": "^5.18.2",
|
||||||
|
"zod": "^3.25.76"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"vitest": "^3.2.7"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
# yaml-language-server: $schema=https://unpkg.com/@sveltia/cms/schema/sveltia-cms.json
|
||||||
|
|
||||||
|
# 本地验证用本地 Gitea(Task 5 起的);生产换真实 Gitea 地址
|
||||||
|
backend:
|
||||||
|
name: gitea
|
||||||
|
repo: root/official-site-base
|
||||||
|
base_url: http://localhost:3001
|
||||||
|
api_root: http://localhost:3001/api/v1
|
||||||
|
|
||||||
|
media_folder: /public/media
|
||||||
|
public_folder: /media
|
||||||
|
|
||||||
|
collections:
|
||||||
|
- name: news
|
||||||
|
label: 新闻动态
|
||||||
|
label_singular: 新闻
|
||||||
|
folder: /src/content/news
|
||||||
|
create: true
|
||||||
|
slug: '{{slug}}'
|
||||||
|
fields:
|
||||||
|
- { label: 标题, name: title, widget: string }
|
||||||
|
- { label: 日期, name: date, widget: datetime, type: date }
|
||||||
|
- { label: 分类, name: category, widget: string, default: 未分类 }
|
||||||
|
- { label: 正文, name: body, widget: richtext }
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="robots" content="noindex" />
|
||||||
|
<title>Sveltia CMS</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<!-- 注意:不要加 type="module",不要加 CSS link(Sveltia 自带样式) -->
|
||||||
|
<script src="https://unpkg.com/@sveltia/cms/dist/sveltia-cms.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><rect width="32" height="32" rx="6" fill="#007aff"/><text x="16" y="22" font-size="18" text-anchor="middle" fill="#fff" font-family="sans-serif">O</text></svg>
|
||||||
|
After Width: | Height: | Size: 220 B |
@@ -0,0 +1,30 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# 判空:没配凭据时跳过(Phase 0 本地/未配 OSS 时不阻塞)
|
||||||
|
if [ -z "${OSS_ACCESS_KEY_ID:-}" ] || [ -z "${OSS_BUCKET:-}" ]; then
|
||||||
|
echo "[deploy-oss] 未配置 OSS 凭据,跳过同步。"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
: "${OSS_ACCESS_KEY_SECRET:?需 OSS_ACCESS_KEY_SECRET}"
|
||||||
|
: "${OSS_ENDPOINT:?需 OSS_ENDPOINT}"
|
||||||
|
: "${OSS_BUCKET:?需 OSS_BUCKET}"
|
||||||
|
|
||||||
|
DIST_DIR="${1:-dist}"
|
||||||
|
[ -d "$DIST_DIR" ] || { echo "[deploy-oss] $DIST_DIR 不存在,请先 build"; exit 1; }
|
||||||
|
|
||||||
|
echo "[deploy-oss] 同步 $DIST_DIR → oss://$OSS_BUCKET/"
|
||||||
|
ossutil sync "$DIST_DIR" "oss://$OSS_BUCKET/" \
|
||||||
|
-i "$OSS_ACCESS_KEY_ID" \
|
||||||
|
-k "$OSS_ACCESS_KEY_SECRET" \
|
||||||
|
-e "$OSS_ENDPOINT" \
|
||||||
|
--force --delete
|
||||||
|
|
||||||
|
echo "[deploy-oss] 刷新 CDN(如配了 CDN_DOMAIN)"
|
||||||
|
if [ -n "${CDN_DOMAIN:-}" ]; then
|
||||||
|
ossutil cdn refresh --dirs "https://${CDN_DOMAIN}/" \
|
||||||
|
-i "$OSS_ACCESS_KEY_ID" -k "$OSS_ACCESS_KEY_SECRET" -e "$OSS_ENDPOINT" || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "[deploy-oss] 完成"
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import { defineCollection } from 'astro:content';
|
||||||
|
import { glob } from 'astro/loaders';
|
||||||
|
import { newsSchema } from './content/schemas';
|
||||||
|
|
||||||
|
const news = defineCollection({
|
||||||
|
loader: glob({ pattern: '**/*.md', base: './src/content/news' }),
|
||||||
|
schema: newsSchema,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const collections = { news };
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
title: 官网基座启动
|
||||||
|
date: 2026-07-23
|
||||||
|
category: 公告
|
||||||
|
---
|
||||||
|
|
||||||
|
这是 Phase 0 技术验证的第一条新闻。内容编辑链路跑通后,客户可在 Sveltia CMS 自助新增。
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
export const newsSchema = z.object({
|
||||||
|
title: z.string(),
|
||||||
|
date: z.coerce.date(),
|
||||||
|
category: z.string().default('未分类'),
|
||||||
|
});
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
---
|
||||||
|
import '../styles/global.css';
|
||||||
|
interface Props { title: string; }
|
||||||
|
const { title } = Astro.props;
|
||||||
|
---
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<link rel="icon" href="/favicon.svg" />
|
||||||
|
<title>{title}</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header><a href="/" style="font-weight:700;font-size:1.25rem;">官网基座</a></header>
|
||||||
|
<main><slot /></main>
|
||||||
|
<footer style="margin-top:3rem;font-size:.85rem;color:#888;">© 官网基座</footer>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
---
|
||||||
|
import BaseLayout from '../layouts/BaseLayout.astro';
|
||||||
|
import { getCollection } from 'astro:content';
|
||||||
|
|
||||||
|
const news = (await getCollection('news')).sort(
|
||||||
|
(a, b) => b.data.date.getTime() - a.data.date.getTime()
|
||||||
|
);
|
||||||
|
---
|
||||||
|
<BaseLayout title="官网基座 · 首页">
|
||||||
|
<h1>官网基座</h1>
|
||||||
|
<p>Phase 0 技术验证站点。</p>
|
||||||
|
<section>
|
||||||
|
<h2>最新动态</h2>
|
||||||
|
<ul>
|
||||||
|
{news.map((post) => (
|
||||||
|
<li><a href={`/news/${post.id}/`}>{post.data.title}</a> <small>{post.data.date.toLocaleDateString('zh-CN')}</small></li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
<p><a href="/news/">查看全部 →</a></p>
|
||||||
|
</section>
|
||||||
|
</BaseLayout>
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
---
|
||||||
|
import BaseLayout from '../../layouts/BaseLayout.astro';
|
||||||
|
import { getCollection, render } from 'astro:content';
|
||||||
|
|
||||||
|
export async function getStaticPaths() {
|
||||||
|
const news = await getCollection('news');
|
||||||
|
return news.map((post) => ({
|
||||||
|
params: { slug: post.id },
|
||||||
|
props: { post },
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
const { post } = Astro.props;
|
||||||
|
const { Content } = await render(post);
|
||||||
|
---
|
||||||
|
<BaseLayout title={post.data.title}>
|
||||||
|
<article>
|
||||||
|
<h1>{post.data.title}</h1>
|
||||||
|
<p><small>{post.data.category} · {post.data.date.toLocaleDateString('zh-CN')}</small></p>
|
||||||
|
<Content />
|
||||||
|
</article>
|
||||||
|
<p><a href="/news/">← 返回列表</a></p>
|
||||||
|
</BaseLayout>
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
---
|
||||||
|
import BaseLayout from '../../layouts/BaseLayout.astro';
|
||||||
|
import { getCollection } from 'astro:content';
|
||||||
|
|
||||||
|
const news = (await getCollection('news')).sort(
|
||||||
|
(a, b) => b.data.date.getTime() - a.data.date.getTime()
|
||||||
|
);
|
||||||
|
---
|
||||||
|
<BaseLayout title="新闻动态">
|
||||||
|
<h1>新闻动态</h1>
|
||||||
|
<ul>
|
||||||
|
{news.map((post) => (
|
||||||
|
<li>
|
||||||
|
<a href={`/news/${post.id}/`}>{post.data.title}</a>
|
||||||
|
<small> · {post.data.category} · {post.data.date.toLocaleDateString('zh-CN')}</small>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</BaseLayout>
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
:root {
|
||||||
|
/* 白标入口:换客户时改这里 */
|
||||||
|
--color-primary: #007aff;
|
||||||
|
--color-text: #1a1a1a;
|
||||||
|
--color-bg: #ffffff;
|
||||||
|
--font-sans: system-ui, -apple-system, 'PingFang SC', 'Microsoft YaHei', sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
html { font-family: var(--font-sans); color: var(--color-text); background: var(--color-bg); }
|
||||||
|
body { margin: 0; max-width: 960px; margin-inline: auto; padding: 1.5rem; }
|
||||||
|
a { color: var(--color-primary); }
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import { describe, it, expect } from 'vitest';
|
||||||
|
import { newsSchema } from '../src/content/schemas';
|
||||||
|
|
||||||
|
describe('newsSchema', () => {
|
||||||
|
it('accepts valid frontmatter and coerces date', () => {
|
||||||
|
const parsed = newsSchema.parse({
|
||||||
|
title: '官网基座启动',
|
||||||
|
date: '2026-07-23',
|
||||||
|
category: '公告',
|
||||||
|
});
|
||||||
|
expect(parsed.title).toBe('官网基座启动');
|
||||||
|
expect(parsed.date).toEqual(new Date('2026-07-23'));
|
||||||
|
expect(parsed.category).toBe('公告');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('defaults category to 未分类', () => {
|
||||||
|
const parsed = newsSchema.parse({ title: '测试', date: '2026-07-23' });
|
||||||
|
expect(parsed.category).toBe('未分类');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rejects missing title', () => {
|
||||||
|
expect(() => newsSchema.parse({ date: '2026-07-23' })).toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rejects missing date', () => {
|
||||||
|
expect(() => newsSchema.parse({ title: '测试' })).toThrow();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"extends": "astro/tsconfigs/strict",
|
||||||
|
"include": [".astro/types.d.ts", "**/*"],
|
||||||
|
"exclude": ["dist"]
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import { defineConfig } from 'vitest/config';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
test: {
|
||||||
|
include: ['tests/**/*.test.ts'],
|
||||||
|
},
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user