Compare commits

...
16 Commits
Author SHA1 Message Date
root 83db3293af Create 新闻 “sveltia-可视化创建测试”
build-and-deploy / build (push) Successful in 53s
2026-07-23 18:09:34 +00:00
root 1be8a687d5 feat: CMS test news
build-and-deploy / build (push) Successful in 47s
2026-07-23 17:59:29 +00:00
lzy 76d70282b7 feat(cms): Sveltia 接生产 Gitea (gitea.shoux.net OAuth)
build-and-deploy / build (push) Successful in 46s
2026-07-24 01:57:19 +08:00
lzy dcebc1ec9c ci: 构建后自动部署 dist 到 test.shoux.net
build-and-deploy / build (push) Successful in 50s
2026-07-24 01:43:57 +08:00
lzy 71d0721290 ci: 去掉 ossutil version(v1.7 无此子命令)
build-and-deploy / build (push) Successful in 45s
2026-07-24 00:59:59 +08:00
lzy 88724d4692 ci: workflow 加 ossutil 安装与 OSS 同步步骤
build-and-deploy / build (push) Failing after 47s
2026-07-24 00:56:49 +08:00
lzy 57cdbe1b5b ci: raw workflow + 配置适配华为云部署
build-and-deploy / build (push) Successful in 38s
2026-07-24 00:54:01 +08:00
lzy 60e18ded88 fix(docker): 修复本地 Gitea+act_runner 配置使 CI 可跑通
- 端口 3000→3001(避开 Astro dev server 占用)
- gitea 加 INSTALL_LOCK=true 跳过首装页 + ROOT_URL=http://localhost:3001/
- registration token 两端同步并满足 Gitea ≥32 字符强度要求
- runner 加 GITEA_RUNNER_LABELS 走 docker 模式(node:20-bookworm)
- docker.sock 保持单斜杠(Docker Desktop 实测可用,双斜杠为伪需求)

端到端验证通过:Gitea 1.27 + act_runner v0.6.1,标准 node 镜像
job 经 docker.sock spawn 容器执行成功。需配合 Docker registry
mirror(国内)拉取业务镜像。
2026-07-23 23:03:06 +08:00
lzy d72e9e02e3 docs: 记录 Phase 0 执行偏差与现状(版本/阻塞) 2026-07-23 19:34:16 +08:00
lzy d7d60cc34b chore: 本地 Gitea+act_runner docker-compose 及数据目录忽略 2026-07-23 19:18:01 +08:00
lzy 661e80fade feat: ossutil 发布脚本(本地与 CI 共用) 2026-07-23 19:17:09 +08:00
lzy c74b2331bc ci: Gitea Action 构建并发布到 OSS 2026-07-23 19:17:09 +08:00
lzy eacc847c6a feat: 接入 Sveltia CMS(/admin + gitea backend 配置) 2026-07-23 19:16:20 +08:00
lzy c80ada6af5 feat: 首页与新闻列表/详情页面 2026-07-23 19:15:27 +08:00
lzy eab95953bf feat: 新增 news 内容集合与 Zod schema(含单测) 2026-07-23 19:14:38 +08:00
lzy 3ecac5b757 feat: 初始化 Astro 项目与工具链(astro5/vitest/zod3/设计token/env) 2026-07-23 19:12:51 +08:00
25 changed files with 6532 additions and 0 deletions
+9
View File
@@ -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
+46
View File
@@ -0,0 +1,46 @@
name: build-and-deploy
on:
push:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
# raw checkout:绕开 actions/checkout(服务器拉 github action 不通)
# 仓库 publicjob 容器经 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/
# 部署到 test.shoux.netjob 容器挂载宿主 /var/www/test-shoux-net → /deploy
- name: Deploy to test.shoux.net
run: |
rm -rf /deploy/*
cp -r dist/. /deploy/
echo "[deploy] dist -> /deploy (host /var/www/test-shoux-net)"
- name: Install ossutil
run: |
curl -fsSL -o /tmp/ossutilinstall.sh https://gosspublic.alicdn.com/ossutil/install.sh
bash /tmp/ossutilinstall.sh
- name: Sync to OSS
env:
OSS_ACCESS_KEY_ID: ${{ secrets.OSS_ACCESS_KEY_ID }}
OSS_ACCESS_KEY_SECRET: ${{ secrets.OSS_ACCESS_KEY_SECRET }}
OSS_ENDPOINT: ${{ secrets.OSS_ENDPOINT }}
OSS_BUCKET: ${{ secrets.OSS_BUCKET }}
run: bash scripts/deploy-oss.sh
+4
View File
@@ -30,3 +30,7 @@ Thumbs.db
.vscode/
.idea/
*.swp
# ===== 本地 Gitea/runner 数据(docker compose 产生)=====
docker/gitea-data/
docker/runner-data/
+5
View File
@@ -0,0 +1,5 @@
import { defineConfig } from 'astro/config';
export default defineConfig({
site: 'https://example.com',
});
+36
View File
@@ -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 tokenrunner 用它完成注册
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 注明了降级方案)
- `act_runner` 注册 tokencompose 写死 `phase0-local-token`,仅本地)
- 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) 依赖 rolldownWindows native binding 装不上 |
| zod | latest(4) | **3.25.76** | astro 5 内置 zod 3zod 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。
**阻塞(待用户决策):** 本机无 DockerTask 5(起 Gitea/ 6OAuth/ 9(端到端)无法本地跑通。Gitea 部署方式三选一:① 本地装 Docker Desktop ② 本地 Gitea 二进制 ③ 上服务器(ops 机或新轻量云)。
+6153
View File
File diff suppressed because it is too large Load Diff
+19
View File
@@ -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"
}
}
+25
View File
@@ -0,0 +1,25 @@
# yaml-language-server: $schema=https://unpkg.com/@sveltia/cms/schema/sveltia-cms.json
# Sveltia CMS backend:自建 Giteahttps://gitea.shoux.netOAuth2 PKCE
backend:
name: gitea
repo: root/official-site-base
base_url: https://gitea.shoux.net
api_root: https://gitea.shoux.net/api/v1
app_id: b6fe6225-8700-4599-909e-98a8c5a8defa
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 }
+12
View File
@@ -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 linkSveltia 自带样式) -->
<script src="https://unpkg.com/@sveltia/cms/dist/sveltia-cms.js"></script>
</body>
</html>
+1
View File
@@ -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

+30
View File
@@ -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] 完成"
+10
View File
@@ -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 };
+6
View File
@@ -0,0 +1,6 @@
---
title: CMS 自助发布测试
date: 2026-07-24
category: 公告
---
这是经 CMS 链路提交的测试新闻(模拟 Sveltia 编辑→Gitea→CI 自动部署→test.shoux.net)。
@@ -0,0 +1,7 @@
---
title: Sveltia 可视化创建测试
date: 2026-07-24
category: 公告
---
这条新闻由 Sveltia CMS 可视化创建,验证用户登录→编辑→发布完整闭环。
+7
View File
@@ -0,0 +1,7 @@
---
title: 官网基座启动
date: 2026-07-23
category: 公告
---
这是 Phase 0 技术验证的第一条新闻。内容编辑链路跑通后,客户可在 Sveltia CMS 自助新增。
+7
View File
@@ -0,0 +1,7 @@
import { z } from 'zod';
export const newsSchema = z.object({
title: z.string(),
date: z.coerce.date(),
category: z.string().default('未分类'),
});
+19
View File
@@ -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>
+21
View File
@@ -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>
+23
View File
@@ -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>
+19
View File
@@ -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>
+11
View File
@@ -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); }
+28
View File
@@ -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();
});
});
+5
View File
@@ -0,0 +1,5 @@
{
"extends": "astro/tsconfigs/strict",
"include": [".astro/types.d.ts", "**/*"],
"exclude": ["dist"]
}
+7
View File
@@ -0,0 +1,7 @@
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
include: ['tests/**/*.test.ts'],
},
});