Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4746787484 | |||
| 61d80f352f | |||
| 4ec83f2603 | |||
| 51347669a4 | |||
| 6e8dc8942a | |||
| 5d74251032 |
@@ -1,24 +0,0 @@
|
|||||||
.git
|
|
||||||
**/.next
|
|
||||||
**/node_modules
|
|
||||||
**/dist
|
|
||||||
coverage
|
|
||||||
.env
|
|
||||||
.env.*
|
|
||||||
!.env.example
|
|
||||||
*.tsbuildinfo
|
|
||||||
.DS_Store
|
|
||||||
.agents
|
|
||||||
.codex
|
|
||||||
|
|
||||||
# Documentation and CI metadata are not application build inputs.
|
|
||||||
/docs
|
|
||||||
/.gitea
|
|
||||||
/README.md
|
|
||||||
/AGENTS.md
|
|
||||||
|
|
||||||
**/__pycache__
|
|
||||||
**/.venv
|
|
||||||
|
|
||||||
# Local authoring assets are uploaded via APIs, never application build inputs.
|
|
||||||
/content
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
name: Endoleap brand checks
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
branches: [main]
|
|
||||||
paths:
|
|
||||||
- 'packages/brand/**'
|
|
||||||
- 'pnpm-workspace.yaml'
|
|
||||||
- 'package.json'
|
|
||||||
- 'AGENTS.md'
|
|
||||||
- '.gitea/workflows/brand.yml'
|
|
||||||
push:
|
|
||||||
branches: [main]
|
|
||||||
paths:
|
|
||||||
- 'packages/brand/**'
|
|
||||||
- 'pnpm-workspace.yaml'
|
|
||||||
- '.gitea/workflows/brand.yml'
|
|
||||||
concurrency:
|
|
||||||
group: brand-${{ gitea.event.pull_request.number || gitea.ref }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
jobs:
|
|
||||||
verify:
|
|
||||||
runs-on: ces-control
|
|
||||||
timeout-minutes: 20
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- name: Verify brand assets, Tailwind compilation and typed exports in Docker
|
|
||||||
run: docker build -f packages/brand/Dockerfile -t "endoleap-brand-check:${{ gitea.sha }}" .
|
|
||||||
@@ -1,80 +0,0 @@
|
|||||||
name: creative workspace review checks
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
branches: [main]
|
|
||||||
paths:
|
|
||||||
- 'apps/studio/**'
|
|
||||||
- 'apps/waterfall-wiki/**'
|
|
||||||
- '.gitea/workflows/**'
|
|
||||||
- 'scripts/*studio*'
|
|
||||||
- 'compose.creative-review.yml'
|
|
||||||
- 'scripts/integration.sh'
|
|
||||||
- 'scripts/cleanup-integration.sh'
|
|
||||||
- 'tests/test_integration_cleanup.py'
|
|
||||||
- 'scripts/cleanup-review-images.sh'
|
|
||||||
- 'tests/test_review_image_cleanup.py'
|
|
||||||
concurrency:
|
|
||||||
group: creative-workspace-${{ gitea.event.pull_request.number }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
jobs:
|
|
||||||
verify-deployed-canvas:
|
|
||||||
runs-on: ces-control
|
|
||||||
timeout-minutes: 15
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- name: Verify production acceptance against the designated deployed server
|
|
||||||
run: |
|
|
||||||
node --check scripts/verify-studio-visual.cjs
|
|
||||||
node --check apps/studio/scripts/infinite-canvas-smoke.cjs
|
|
||||||
bash -n scripts/verify-studio-production.sh
|
|
||||||
bash scripts/verify-studio-production.sh
|
|
||||||
- name: Save server acceptance evidence
|
|
||||||
id: acceptance_evidence
|
|
||||||
if: always()
|
|
||||||
uses: actions/upload-artifact@v3
|
|
||||||
with:
|
|
||||||
name: studio-production-acceptance
|
|
||||||
path: artifacts/studio-acceptance/
|
|
||||||
if-no-files-found: ignore
|
|
||||||
retention-days: 7
|
|
||||||
- name: Remove temporary acceptance output after upload
|
|
||||||
if: always() && steps.acceptance_evidence.outcome == 'success'
|
|
||||||
run: rm -rf artifacts/studio-acceptance
|
|
||||||
verify:
|
|
||||||
runs-on: ces-control
|
|
||||||
timeout-minutes: 60
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
- name: Check patch formatting
|
|
||||||
run: |
|
|
||||||
git diff --check HEAD^ HEAD
|
|
||||||
bash -n scripts/integration.sh scripts/cleanup-integration.sh scripts/verify-studio-production.sh scripts/cleanup-review-images.sh
|
|
||||||
node --check scripts/verify-studio-visual.cjs
|
|
||||||
python3 tests/test_integration_cleanup.py
|
|
||||||
python3 tests/test_review_image_cleanup.py
|
|
||||||
node --test apps/waterfall-wiki/tests/brand-navigation.test.mjs
|
|
||||||
- name: Verify Studio types, tests, build and smoke
|
|
||||||
run: docker build -f apps/studio/Dockerfile -t "creative-studio-review:${{ gitea.sha }}" .
|
|
||||||
- name: Build Wiki integration and runtime images
|
|
||||||
run: |
|
|
||||||
docker build --target integration -t "creative-wiki-tests:${{ gitea.sha }}" .
|
|
||||||
docker build --target runtime -t "creative-wiki-review:${{ gitea.sha }}" .
|
|
||||||
- name: Verify isolated PostgreSQL, S3 and browser integration
|
|
||||||
env:
|
|
||||||
STUDIO_REVIEW_IMAGE: creative-studio-review:${{ gitea.sha }}
|
|
||||||
WATERFALL_WIKI_IMAGE: creative-wiki-review:${{ gitea.sha }}
|
|
||||||
WATERFALL_WIKI_TEST_IMAGE: creative-wiki-tests:${{ gitea.sha }}
|
|
||||||
GITEA_RUN_ID: ${{ gitea.run_id }}
|
|
||||||
run: bash scripts/integration.sh
|
|
||||||
- name: Clean this run disposable integration resources even after cancellation
|
|
||||||
if: always()
|
|
||||||
env:
|
|
||||||
GITEA_RUN_ID: ${{ gitea.run_id }}
|
|
||||||
run: bash scripts/cleanup-integration.sh
|
|
||||||
- name: Remove this run disposable review image tags
|
|
||||||
if: always()
|
|
||||||
env:
|
|
||||||
REVIEW_SHA: ${{ gitea.sha }}
|
|
||||||
run: bash scripts/cleanup-review-images.sh
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
name: python CI and deployment
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [main]
|
|
||||||
paths:
|
|
||||||
- 'apps/python/**'
|
|
||||||
- 'scripts/deploy-python.sh'
|
|
||||||
- '.gitea/workflows/python.yml'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
verify-and-deploy:
|
|
||||||
if: gitea.ref == 'refs/heads/main' && gitea.event_name == 'push'
|
|
||||||
runs-on: ces-control
|
|
||||||
timeout-minutes: 45
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
- name: Build and verify python
|
|
||||||
run: docker build -f apps/python/Dockerfile -t "prompt-python:${{ gitea.sha }}" .
|
|
||||||
- name: Release python
|
|
||||||
if: gitea.ref == 'refs/heads/main' && gitea.event_name == 'push'
|
|
||||||
run: bash scripts/deploy-python.sh "${{ gitea.sha }}"
|
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
name: creative workspace CI and deployment
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [main]
|
|
||||||
paths:
|
|
||||||
- 'apps/waterfall-wiki/**'
|
|
||||||
- 'apps/studio/**'
|
|
||||||
- 'scripts/*studio*'
|
|
||||||
- 'content/studio-examples/**'
|
|
||||||
- 'content/studio-tools/layouts-api.md'
|
|
||||||
- '.gitea/workflows/waterfall-wiki.yml'
|
|
||||||
- 'Dockerfile'
|
|
||||||
- 'compose.yml'
|
|
||||||
- 'scripts/deploy.sh'
|
|
||||||
- 'scripts/wiki-image-lifecycle.py'
|
|
||||||
- 'scripts/infra.sh'
|
|
||||||
- 'scripts/auth-admin.sh'
|
|
||||||
- 'scripts/integration.sh'
|
|
||||||
- 'scripts/cleanup-integration.sh'
|
|
||||||
- 'tests/test_integration_cleanup.py'
|
|
||||||
- 'pnpm-workspace.yaml'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
verify-and-deploy:
|
|
||||||
if: gitea.ref == 'refs/heads/main' && gitea.event_name == 'push'
|
|
||||||
runs-on: ces-control
|
|
||||||
timeout-minutes: 60
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
- name: Check shared content and documentation contracts
|
|
||||||
run: |
|
|
||||||
python3 tests/test_integration_cleanup.py
|
|
||||||
bash -n scripts/integration.sh scripts/cleanup-integration.sh
|
|
||||||
node scripts/check-studio-contracts.mjs
|
|
||||||
node --check scripts/export-studio-series.cjs
|
|
||||||
node --check scripts/save-studio-series.mjs
|
|
||||||
bash -n scripts/publish-studio-guide.sh scripts/deliver-studio-series.sh
|
|
||||||
- name: Build and verify Studio
|
|
||||||
run: docker build -f apps/studio/Dockerfile -t "prompt-studio:${{ gitea.sha }}" .
|
|
||||||
- name: Build, verify, deploy and retain wiki images
|
|
||||||
env:
|
|
||||||
RELEASE_SHA: ${{ gitea.sha }}
|
|
||||||
run: |
|
|
||||||
if [ "$(id -u)" -eq 0 ]; then
|
|
||||||
python3 scripts/wiki-image-lifecycle.py ci --sha "$RELEASE_SHA"
|
|
||||||
else
|
|
||||||
sudo -n python3 scripts/wiki-image-lifecycle.py ci --sha "$RELEASE_SHA"
|
|
||||||
fi
|
|
||||||
- name: Release Studio after Wiki routes are deployed
|
|
||||||
run: bash scripts/deploy-studio.sh "${{ gitea.sha }}"
|
|
||||||
- name: Verify the deployed creative workspace
|
|
||||||
run: bash scripts/verify-studio-production.sh
|
|
||||||
- name: Save server acceptance evidence
|
|
||||||
id: acceptance_evidence
|
|
||||||
if: always()
|
|
||||||
uses: actions/upload-artifact@v3
|
|
||||||
with:
|
|
||||||
name: studio-production-acceptance
|
|
||||||
path: artifacts/studio-acceptance/
|
|
||||||
if-no-files-found: ignore
|
|
||||||
retention-days: 7
|
|
||||||
- name: Remove temporary acceptance output after upload
|
|
||||||
if: always() && steps.acceptance_evidence.outcome == 'success'
|
|
||||||
run: rm -rf artifacts/studio-acceptance
|
|
||||||
- name: Clean this run disposable integration resources even after cancellation
|
|
||||||
if: always()
|
|
||||||
env:
|
|
||||||
GITEA_RUN_ID: ${{ gitea.run_id }}
|
|
||||||
run: bash scripts/cleanup-integration.sh
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
name: Wiki image retention checks
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [main]
|
|
||||||
paths:
|
|
||||||
- 'scripts/wiki-image-lifecycle.py'
|
|
||||||
- 'tests/test_wiki_image_lifecycle.py'
|
|
||||||
- '.gitea/workflows/wiki-image-retention-checks.yml'
|
|
||||||
jobs:
|
|
||||||
retention-policy:
|
|
||||||
if: gitea.ref == 'refs/heads/main' && gitea.event_name == 'push'
|
|
||||||
runs-on: ces-control
|
|
||||||
timeout-minutes: 5
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- name: Check retention without building or accessing the Docker daemon
|
|
||||||
run: |
|
|
||||||
bash -n scripts/deploy.sh
|
|
||||||
PYTHONDONTWRITEBYTECODE=1 python3 -m unittest discover -s tests -p 'test_wiki_image_lifecycle.py' -v
|
|
||||||
+5
-3
@@ -1,4 +1,6 @@
|
|||||||
node_modules/
|
node_modules/
|
||||||
|
.pnpm-store/
|
||||||
|
.turbo/
|
||||||
.next/
|
.next/
|
||||||
coverage/
|
coverage/
|
||||||
.env*
|
.env*
|
||||||
@@ -25,6 +27,6 @@ __pycache__/
|
|||||||
/apps/studio/public/brand/
|
/apps/studio/public/brand/
|
||||||
/apps/studio/public/favicon.ico
|
/apps/studio/public/favicon.ico
|
||||||
/apps/studio/public/apple-touch-icon.png
|
/apps/studio/public/apple-touch-icon.png
|
||||||
/apps/waterfall-wiki/public/brand/
|
/apps/dashboard/public/brand/
|
||||||
/apps/waterfall-wiki/public/favicon.ico
|
/apps/dashboard/public/favicon.ico
|
||||||
/apps/waterfall-wiki/public/apple-touch-icon.png
|
/apps/dashboard/public/apple-touch-icon.png
|
||||||
|
|||||||
@@ -8,14 +8,6 @@ This block is written and re-added by `next dev` — verify at `node_modules/nex
|
|||||||
|
|
||||||
<!-- END:nextjs-agent-rules -->
|
<!-- END:nextjs-agent-rules -->
|
||||||
|
|
||||||
## 构建与验收
|
## 项目维护
|
||||||
|
|
||||||
- 在 ces-control 上执行构建、产物生成和需要编译的检查。本地可执行差异审查及无需构建的轻量检查。
|
- Wiki 和 Studio 共用 `apps/dashboard` 的 Pages Router、登录保护和品牌包。保留已发布的路由、媒体标识和数据库迁移。
|
||||||
- 页面改动经主线部署后,在指定的 [服务器入口](http://8.211.154.134:18131/) 验收。不要启动本地网页预览。
|
|
||||||
- CI 触发条件与发布路径见 [构建与入口](docs/ci-and-entrypoints.md)。
|
|
||||||
|
|
||||||
## 品牌
|
|
||||||
|
|
||||||
- 修改品牌色、主题或 Logo 前,查阅 [共享品牌包](packages/brand/README.md)。
|
|
||||||
- 使用 home-light、home-dark、canvas-light、canvas-dark 四套主题。画布背景保持中性近白或近黑,用户素材保持原色。
|
|
||||||
- 色板和 Logo 轮廓分别以品牌包的 `tokens/palette.json` 和 `tokens/geometry.json` 为准,更新时保持 CSS、类型、资产和清单一致。
|
|
||||||
|
|||||||
-41
@@ -1,41 +0,0 @@
|
|||||||
FROM node:24-bookworm-slim AS base
|
|
||||||
LABEL io.entroia.project="waterfall-wiki"
|
|
||||||
ENV NEXT_TELEMETRY_DISABLED=1
|
|
||||||
WORKDIR /app
|
|
||||||
RUN apt-get update \
|
|
||||||
&& apt-get install -y --no-install-recommends ffmpeg ca-certificates chromium fonts-noto-cjk \
|
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
FROM base AS dependencies
|
|
||||||
WORKDIR /repo/apps/waterfall-wiki
|
|
||||||
LABEL io.entroia.wiki.build="true"
|
|
||||||
RUN corepack enable && corepack prepare pnpm@11.25.0 --activate
|
|
||||||
COPY apps/waterfall-wiki/pnpm-lock.yaml ./
|
|
||||||
COPY pnpm-workspace.yaml /repo/pnpm-workspace.yaml
|
|
||||||
COPY packages/brand/ /repo/packages/brand/
|
|
||||||
COPY apps/waterfall-wiki/package.json ./
|
|
||||||
RUN pnpm --filter @prompt/waterfall-wiki install --frozen-lockfile --fetch-timeout=300000 --network-concurrency=8
|
|
||||||
|
|
||||||
FROM dependencies AS build
|
|
||||||
# .dockerignore excludes docs and operational notes without extra legacy COPY snapshots.
|
|
||||||
COPY apps/waterfall-wiki/ .
|
|
||||||
RUN pnpm typecheck && pnpm test && pnpm build && pnpm test:smoke
|
|
||||||
|
|
||||||
FROM build AS integration
|
|
||||||
USER node
|
|
||||||
CMD ["npm", "run", "test:integration"]
|
|
||||||
|
|
||||||
FROM base AS runtime
|
|
||||||
WORKDIR /app/apps/waterfall-wiki
|
|
||||||
ENV NODE_ENV=production HOSTNAME=0.0.0.0 PORT=3000 \
|
|
||||||
FFMPEG_PATH=/usr/bin/ffmpeg FFPROBE_PATH=/usr/bin/ffprobe
|
|
||||||
COPY --from=build --chown=node:node /repo/apps/waterfall-wiki/.next/standalone/ /app/
|
|
||||||
COPY --from=build --chown=node:node /repo/apps/waterfall-wiki/dist/worker.cjs /repo/apps/waterfall-wiki/dist/bootstrap.cjs /repo/apps/waterfall-wiki/dist/publish-learning-series.cjs /repo/apps/waterfall-wiki/dist/auth-cli.cjs ./
|
|
||||||
COPY --from=build --chown=node:node /repo/apps/waterfall-wiki/db/ ./db/
|
|
||||||
COPY --chown=node:node apps/waterfall-wiki/scripts/container-health.mjs ./
|
|
||||||
COPY --chown=node:node apps/waterfall-wiki/scripts/import-character-packs.mjs apps/waterfall-wiki/scripts/verify-character-pack.mjs ./scripts/
|
|
||||||
COPY --chown=node:node apps/waterfall-wiki/seed/characters/ ./seed/characters/
|
|
||||||
COPY --chown=node:node apps/waterfall-wiki/src/content/managed-media.json ./src/content/managed-media.json
|
|
||||||
USER node
|
|
||||||
EXPOSE 3000
|
|
||||||
CMD ["node", "server.js"]
|
|
||||||
@@ -1,42 +1,38 @@
|
|||||||
# 啡跃 Endoleap
|
# 啡跃 Endoleap
|
||||||
|
|
||||||
啡跃把笔记、参考素材和创作工作台放在同一个网站。知识库用于浏览、搜索和发布内容,Studio 提供图文排版与无限画布。
|
啡跃把笔记、参考素材和创作工作台放在同一个 Next.js 应用里。应用位于 `apps/dashboard`,共享主题与品牌资源位于 `packages/brand`。
|
||||||
|
|
||||||
[打开啡跃](http://8.211.154.134:18131/)
|
|
||||||
|
|
||||||
| 入口 | 用途 |
|
| 入口 | 用途 |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| `/` | 浏览内容,打开 `/explore/<id>` 查看单篇笔记 |
|
| `/` | 浏览内容,打开 `/explore/<id>` 查看笔记 |
|
||||||
| `/guide` | 浏览指导文档 |
|
| `/guide` | 浏览指导文档 |
|
||||||
| `/graphic` | 打开图文工作台 |
|
| `/graphic` | 图文排版 |
|
||||||
| `/infinite_canvas` | 打开无限画布 |
|
| `/infinite_canvas` | 无限画布 |
|
||||||
| `/api?agent_mode=True` | 查看业务 API 目录 |
|
| `/api?agent_mode=True` | 业务 API 目录 |
|
||||||
|
| `/studio/api` | Studio API 目录 |
|
||||||
|
|
||||||
自动化客户端可以在支持的页面或业务接口上加 `?agent_mode=True` 获取纯文本。读写权限与认证要求见对应接口说明。
|
## 运行
|
||||||
|
|
||||||
## 仓库结构
|
准备 Node.js 24、pnpm 11.25.0、PostgreSQL、S3 兼容对象存储和 FFmpeg。参考 [环境变量示例](apps/dashboard/.env.example),将变量注入 Web、初始化和媒体 worker 进程。邮箱注册与找回密码需要可用的 SMTP 服务。
|
||||||
|
|
||||||
| 路径 | 职责 |
|
在仓库根目录运行 `pnpm install --frozen-lockfile` 和 `pnpm build`。然后在 `apps/dashboard` 目录准备数据库和存储,初始化管理员认证并启动 Web。
|
||||||
| --- | --- |
|
|
||||||
| `apps/waterfall-wiki` | Next.js Pages Router 主站、账户、内容和媒体 API,以及后台媒体处理 |
|
|
||||||
| `apps/studio` | Next.js Pages Router 创作工作台及渲染 API,由主站提供统一入口 |
|
|
||||||
| `apps/python` | 按需运行的 PNG 和视频导出工具 |
|
|
||||||
| `packages/brand` | 共享主题、品牌资产及接入规范 |
|
|
||||||
| `content` | 排版源数据、发布教程和示例,供作者工具与契约检查读取 |
|
|
||||||
| `scripts` | 构建部署、运维与内容发布工具 |
|
|
||||||
| `tests` | 跨应用的仓库媒体与运维检查。应用自身的测试位于各自目录 |
|
|
||||||
|
|
||||||
## 验证与发布
|
```sh
|
||||||
|
node dist/bootstrap.cjs
|
||||||
|
node dist/auth-cli.cjs init
|
||||||
|
pnpm start
|
||||||
|
```
|
||||||
|
|
||||||
JavaScript 工作区使用 Node.js 24 和 pnpm 11.25.0。生产服务由 Docker Compose 编排,数据保存在 PostgreSQL 和 S3 兼容对象存储中。
|
媒体 worker 在同一目录作为独立进程运行 `node dist/worker.cjs`。绑定管理员验证器时,在远程交互终端执行 `node dist/auth-cli.cjs qr`。已有实例复用原数据库、对象存储和私有认证文件。
|
||||||
|
|
||||||
需要编译或生成产物的验证在 ces-control 执行。发布工作流由匹配路径的 `main` 推送触发。页面验收使用部署后的指定服务器,详见 [AGENTS.md](AGENTS.md) 和 [构建与发布说明](docs/ci-and-entrypoints.md)。
|
## Monorepo 与 Turborepo
|
||||||
|
|
||||||
## 维护文档
|
仓库在现有 pnpm workspace 上接入 Turborepo,无需运行面向新项目的 `npx create-turbo@latest`。应用仍位于 `apps/dashboard`,源码品牌包仍位于 `packages/brand`;子包脚本和应用产物路径保持原样。依赖统一锁定在根目录 `pnpm-lock.yaml`,安装从仓库根目录执行。
|
||||||
|
|
||||||
- [业务 API](docs/api.md)与[数据模型](docs/data-model.md)
|
根目录命令由 Turbo 调度:`pnpm dev` 启动开发任务,`pnpm dev:dashboard` 仅启动 Wiki,`pnpm build` 构建应用及 worker/CLI,`pnpm typecheck` 生成 Next.js 类型并检查 TypeScript。`pnpm start` 继续直接启动现有应用。也可以在子包目录使用原有命令。
|
||||||
- [动态码登录与运维](docs/dynamic-code-login.md)
|
|
||||||
- [指导文档入口](docs/guidance.md)与[发布协议](docs/linked-guidance/publishing.md)
|
锁文件合并保留应用原有依赖解析。唯一重叠差异是 `@tailwindcss/postcss` 的间接 PostCSS 依赖:品牌包原为 8.5.32,现在沿用应用的 8.5.31;品牌包直接声明的 PostCSS 8.5.6 不变。
|
||||||
- [对象存储](docs/s3-media.md)与[镜像保留策略](docs/wiki-image-retention.md)
|
|
||||||
- [Studio 排版 API 与示例](content/studio-tools/layouts-api.md)
|
迁移阶段所有任务禁用缓存,根命令使用 `--env-mode=loose` 透传既有环境变量,保留品牌资源准备步骤的每次执行。后续启用缓存前,需明确环境变量输入、跨包资源和所有输出。当前不配置 CI/CD,也不自动发布。
|
||||||
- [共享品牌包](packages/brand/README.md)
|
|
||||||
|
仅检查任务计划、不执行编译或生成应用产物时,可运行 `pnpm build --dry=json` 和 `pnpm typecheck --dry=json`。完整构建与运行验证另行执行。
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
# Export to the web, storage bootstrap and worker processes.
|
||||||
|
DATABASE_URL=postgresql://endoleap:replace-me@127.0.0.1:5432/endoleap
|
||||||
|
S3_ENDPOINT=http://127.0.0.1:9000
|
||||||
|
S3_ACCESS_KEY=replace-me
|
||||||
|
S3_SECRET_KEY=replace-me
|
||||||
|
S3_BUCKET=prompt
|
||||||
|
PORT=3000
|
||||||
|
AUTH_CONFIG_FILE=/path/to/private/auth.json
|
||||||
|
AUTH_COOKIE_SECURE=true
|
||||||
|
ACCOUNT_PUBLIC_ORIGIN=https://endoleap.example
|
||||||
|
ACCOUNT_TRUST_PROXY=false
|
||||||
|
|
||||||
|
# Email verification and recovery
|
||||||
|
SMTP_HOST=
|
||||||
|
SMTP_PORT=465
|
||||||
|
SMTP_USERNAME=
|
||||||
|
SMTP_FROM=
|
||||||
|
SMTP_PASSWORD=
|
||||||
@@ -11,10 +11,11 @@ const nextConfig: NextConfig = {
|
|||||||
{source:'/studio',destination:'/workspace',permanent:false},
|
{source:'/studio',destination:'/workspace',permanent:false},
|
||||||
]},
|
]},
|
||||||
async rewrites(){return [
|
async rewrites(){return [
|
||||||
...Object.entries(managedMedia).filter(([path])=>path.startsWith("apps/waterfall-wiki/public/")).map(([path,asset])=>({source:"/"+path.slice("apps/waterfall-wiki/public/".length),destination:"/api/media/"+asset.id})),
|
...Object.entries(managedMedia).filter(([path])=>path.startsWith("apps/dashboard/public/")).map(([path,asset])=>({source:"/"+path.slice("apps/dashboard/public/".length),destination:"/api/media/"+asset.id})),
|
||||||
{source:'/graphic',destination:'http://prompt-studio:3000/studio/graphic'},
|
{source:"/studio/api/:path*",destination:"/api/studio-ui/:path*"},
|
||||||
{source:'/infinite_canvas',destination:'http://prompt-studio:3000/studio/infinite_canvas'},
|
{source:"/studio/brand/:path*",destination:"/brand/:path*"},
|
||||||
{source:"/studio/:path*",destination:"http://prompt-studio:3000/studio/:path*"},
|
{source:"/studio/favicon.ico",destination:"/favicon.ico"},
|
||||||
|
{source:"/studio/apple-touch-icon.png",destination:"/apple-touch-icon.png"},
|
||||||
{source:"/index.md",destination:"/api/agent-index"},
|
{source:"/index.md",destination:"/api/agent-index"},
|
||||||
{source:"/skills/index.md",destination:"/api/guidance/skills"},
|
{source:"/skills/index.md",destination:"/api/guidance/skills"},
|
||||||
{source:"/skills/:path+/index.md",destination:"/api/guidance/skills/:path+"},
|
{source:"/skills/:path+/index.md",destination:"/api/guidance/skills/:path+"},
|
||||||
@@ -22,8 +23,6 @@ const nextConfig: NextConfig = {
|
|||||||
{source:"/agent/index.md",destination:"/api/agent-index"},
|
{source:"/agent/index.md",destination:"/api/agent-index"},
|
||||||
{source:"/skills/:skill/:doc.md",destination:"/api/skill-docs/:skill/:doc"},
|
{source:"/skills/:skill/:doc.md",destination:"/api/skill-docs/:skill/:doc"},
|
||||||
]},
|
]},
|
||||||
output: "standalone", outputFileTracingRoot: resolve(process.cwd(), "../.."),
|
|
||||||
outputFileTracingIncludes: {"/*":["./node_modules/playwright-core/**/*"]},
|
|
||||||
poweredByHeader: false,
|
poweredByHeader: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "@prompt/waterfall-wiki",
|
"name": "@prompt/dashboard",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -7,15 +7,11 @@
|
|||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "pnpm brand:prepare && next dev",
|
"dev": "pnpm brand:prepare && next dev",
|
||||||
"build": "pnpm brand:prepare && next build && esbuild src/worker.ts --bundle --platform=node --format=cjs --external:pg-native --external:playwright-core --outfile=dist/worker.cjs && esbuild scripts/bootstrap.ts --bundle --platform=node --format=cjs --external:pg-native --external:playwright-core --outfile=dist/bootstrap.cjs && esbuild scripts/publish-learning-series.ts --bundle --platform=node --format=cjs --external:pg-native --external:playwright-core --outfile=dist/publish-learning-series.cjs && esbuild scripts/auth-cli.ts --bundle --platform=node --format=cjs --external:pg-native --outfile=dist/auth-cli.cjs && node scripts/prepare-standalone.mjs",
|
"build": "pnpm brand:prepare && next build && esbuild src/worker.ts --bundle --platform=node --format=cjs --external:pg-native --external:playwright-core --outfile=dist/worker.cjs && esbuild scripts/bootstrap.ts --bundle --platform=node --format=cjs --external:pg-native --external:playwright-core --outfile=dist/bootstrap.cjs && esbuild scripts/auth-cli.ts --bundle --platform=node --format=cjs --external:pg-native --outfile=dist/auth-cli.cjs",
|
||||||
"start": "next start",
|
"start": "next start",
|
||||||
"typecheck": "next typegen && tsc --noEmit",
|
"typecheck": "next typegen && tsc --noEmit",
|
||||||
"test": "tsx --test tests/api-documentation.test.ts tests/health.test.ts tests/model.test.ts tests/learning-series.test.ts tests/skill-docs.test.ts tests/wiki.test.ts tests/explore.test.ts tests/preview.test.ts tests/note-markdown.test.tsx tests/api-publication.test.ts tests/discovery.test.ts tests/auth.test.ts tests/account.test.ts tests/guidance.test.ts tests/guidance-publishing.test.ts",
|
|
||||||
"test:smoke": "node scripts/smoke.mjs",
|
|
||||||
"storage:prepare": "tsx scripts/bootstrap.ts",
|
"storage:prepare": "tsx scripts/bootstrap.ts",
|
||||||
"content:publish-learning": "tsx scripts/publish-learning-series.ts",
|
|
||||||
"worker": "tsx src/worker.ts",
|
"worker": "tsx src/worker.ts",
|
||||||
"test:integration": "tsx --test tests/integration.test.ts",
|
|
||||||
"brand:prepare": "node ../../packages/brand/scripts/prepare-app.mjs"
|
"brand:prepare": "node ../../packages/brand/scripts/prepare-app.mjs"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
Before Width: | Height: | Size: 451 B After Width: | Height: | Size: 451 B |
|
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 4.9 KiB |
+24
-24
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"apps/waterfall-wiki/public/learning-skills/cover.png": {
|
"apps/dashboard/public/learning-skills/cover.png": {
|
||||||
"id": "a9cafd80-8207-567b-aaa4-ebd250edaab5",
|
"id": "a9cafd80-8207-567b-aaa4-ebd250edaab5",
|
||||||
"key": "repository-media/sha256/8049b5704e44b2ebf3b97cef2495a85628af9a16d933ede2f7e790bc69b5fab1.png",
|
"key": "repository-media/sha256/8049b5704e44b2ebf3b97cef2495a85628af9a16d933ede2f7e790bc69b5fab1.png",
|
||||||
"sha256": "8049b5704e44b2ebf3b97cef2495a85628af9a16d933ede2f7e790bc69b5fab1",
|
"sha256": "8049b5704e44b2ebf3b97cef2495a85628af9a16d933ede2f7e790bc69b5fab1",
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
"width": 1080,
|
"width": 1080,
|
||||||
"height": 1440
|
"height": 1440
|
||||||
},
|
},
|
||||||
"apps/waterfall-wiki/public/skills/shadow-puppet/dancer-example.png": {
|
"apps/dashboard/public/skills/shadow-puppet/dancer-example.png": {
|
||||||
"id": "6514a180-bb28-558d-ac9e-0dd49279a058",
|
"id": "6514a180-bb28-558d-ac9e-0dd49279a058",
|
||||||
"key": "repository-media/sha256/94803894dcb06dae84d3744f963f55e077326d34f103a4fce05e32c2f4ab6895.png",
|
"key": "repository-media/sha256/94803894dcb06dae84d3744f963f55e077326d34f103a4fce05e32c2f4ab6895.png",
|
||||||
"sha256": "94803894dcb06dae84d3744f963f55e077326d34f103a4fce05e32c2f4ab6895",
|
"sha256": "94803894dcb06dae84d3744f963f55e077326d34f103a4fce05e32c2f4ab6895",
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
"width": 680,
|
"width": 680,
|
||||||
"height": 900
|
"height": 900
|
||||||
},
|
},
|
||||||
"apps/waterfall-wiki/public/skills/shadow-puppet/egret-example.png": {
|
"apps/dashboard/public/skills/shadow-puppet/egret-example.png": {
|
||||||
"id": "63aefa65-5431-5a2e-a20c-4293a010410e",
|
"id": "63aefa65-5431-5a2e-a20c-4293a010410e",
|
||||||
"key": "repository-media/sha256/2dd294282bf6c0dda86902cb2be265ba5b89c160a4d5e01ec50f79dac7ac6b82.png",
|
"key": "repository-media/sha256/2dd294282bf6c0dda86902cb2be265ba5b89c160a4d5e01ec50f79dac7ac6b82.png",
|
||||||
"sha256": "2dd294282bf6c0dda86902cb2be265ba5b89c160a4d5e01ec50f79dac7ac6b82",
|
"sha256": "2dd294282bf6c0dda86902cb2be265ba5b89c160a4d5e01ec50f79dac7ac6b82",
|
||||||
@@ -26,7 +26,7 @@
|
|||||||
"width": 1043,
|
"width": 1043,
|
||||||
"height": 1380
|
"height": 1380
|
||||||
},
|
},
|
||||||
"apps/waterfall-wiki/public/skills/street-people/reference.png": {
|
"apps/dashboard/public/skills/street-people/reference.png": {
|
||||||
"id": "26cdf219-7264-5b51-b1bd-b13eaedcb946",
|
"id": "26cdf219-7264-5b51-b1bd-b13eaedcb946",
|
||||||
"key": "repository-media/sha256/e488a52338ac3496e6e51ea4843d99b8ca9cfe3bec4fc8d1ade3985bbb4f4c86.png",
|
"key": "repository-media/sha256/e488a52338ac3496e6e51ea4843d99b8ca9cfe3bec4fc8d1ade3985bbb4f4c86.png",
|
||||||
"sha256": "e488a52338ac3496e6e51ea4843d99b8ca9cfe3bec4fc8d1ade3985bbb4f4c86",
|
"sha256": "e488a52338ac3496e6e51ea4843d99b8ca9cfe3bec4fc8d1ade3985bbb4f4c86",
|
||||||
@@ -35,7 +35,7 @@
|
|||||||
"width": 1200,
|
"width": 1200,
|
||||||
"height": 2133
|
"height": 2133
|
||||||
},
|
},
|
||||||
"apps/waterfall-wiki/public/skills/street-people/sample.png": {
|
"apps/dashboard/public/skills/street-people/sample.png": {
|
||||||
"id": "c1d98b87-a103-5cf6-829a-3f46321db72f",
|
"id": "c1d98b87-a103-5cf6-829a-3f46321db72f",
|
||||||
"key": "repository-media/sha256/2db53d7593010e6c48dab8f212bc3004b02fc278944ea1686f74d6178a700b88.png",
|
"key": "repository-media/sha256/2db53d7593010e6c48dab8f212bc3004b02fc278944ea1686f74d6178a700b88.png",
|
||||||
"sha256": "2db53d7593010e6c48dab8f212bc3004b02fc278944ea1686f74d6178a700b88",
|
"sha256": "2db53d7593010e6c48dab8f212bc3004b02fc278944ea1686f74d6178a700b88",
|
||||||
@@ -44,7 +44,7 @@
|
|||||||
"width": 1024,
|
"width": 1024,
|
||||||
"height": 1536
|
"height": 1536
|
||||||
},
|
},
|
||||||
"apps/waterfall-wiki/seed/characters/original/01-florist.png": {
|
"apps/dashboard/seed/characters/original/01-florist.png": {
|
||||||
"id": "c1d98b87-a103-5cf6-829a-3f46321db72f",
|
"id": "c1d98b87-a103-5cf6-829a-3f46321db72f",
|
||||||
"key": "repository-media/sha256/2db53d7593010e6c48dab8f212bc3004b02fc278944ea1686f74d6178a700b88.png",
|
"key": "repository-media/sha256/2db53d7593010e6c48dab8f212bc3004b02fc278944ea1686f74d6178a700b88.png",
|
||||||
"sha256": "2db53d7593010e6c48dab8f212bc3004b02fc278944ea1686f74d6178a700b88",
|
"sha256": "2db53d7593010e6c48dab8f212bc3004b02fc278944ea1686f74d6178a700b88",
|
||||||
@@ -53,7 +53,7 @@
|
|||||||
"width": 1024,
|
"width": 1024,
|
||||||
"height": 1536
|
"height": 1536
|
||||||
},
|
},
|
||||||
"apps/waterfall-wiki/seed/characters/original/02-reader.png": {
|
"apps/dashboard/seed/characters/original/02-reader.png": {
|
||||||
"id": "85c30364-9644-5d4a-9e83-96e982860bbe",
|
"id": "85c30364-9644-5d4a-9e83-96e982860bbe",
|
||||||
"key": "repository-media/sha256/669c85e689e7e88f8eff9f4bdaf9b4b7816df88c1956e817d82f8fd39bcdf278.png",
|
"key": "repository-media/sha256/669c85e689e7e88f8eff9f4bdaf9b4b7816df88c1956e817d82f8fd39bcdf278.png",
|
||||||
"sha256": "669c85e689e7e88f8eff9f4bdaf9b4b7816df88c1956e817d82f8fd39bcdf278",
|
"sha256": "669c85e689e7e88f8eff9f4bdaf9b4b7816df88c1956e817d82f8fd39bcdf278",
|
||||||
@@ -62,7 +62,7 @@
|
|||||||
"width": 1024,
|
"width": 1024,
|
||||||
"height": 1536
|
"height": 1536
|
||||||
},
|
},
|
||||||
"apps/waterfall-wiki/seed/characters/original/03-cyclist.png": {
|
"apps/dashboard/seed/characters/original/03-cyclist.png": {
|
||||||
"id": "0200f54c-a0a9-50bd-bf04-6512059da0c5",
|
"id": "0200f54c-a0a9-50bd-bf04-6512059da0c5",
|
||||||
"key": "repository-media/sha256/6dedf97b96fafe9f70a16822b0fe9156a073d9149bad5eeab1450323b67ce575.png",
|
"key": "repository-media/sha256/6dedf97b96fafe9f70a16822b0fe9156a073d9149bad5eeab1450323b67ce575.png",
|
||||||
"sha256": "6dedf97b96fafe9f70a16822b0fe9156a073d9149bad5eeab1450323b67ce575",
|
"sha256": "6dedf97b96fafe9f70a16822b0fe9156a073d9149bad5eeab1450323b67ce575",
|
||||||
@@ -71,7 +71,7 @@
|
|||||||
"width": 1347,
|
"width": 1347,
|
||||||
"height": 1167
|
"height": 1167
|
||||||
},
|
},
|
||||||
"apps/waterfall-wiki/seed/characters/original/04-coffee.png": {
|
"apps/dashboard/seed/characters/original/04-coffee.png": {
|
||||||
"id": "ebd511bf-559e-588d-886a-095b785216bd",
|
"id": "ebd511bf-559e-588d-886a-095b785216bd",
|
||||||
"key": "repository-media/sha256/ab337b3a2e5c05406c03aa4a69018191624f18de70081accf740e79993a1835e.png",
|
"key": "repository-media/sha256/ab337b3a2e5c05406c03aa4a69018191624f18de70081accf740e79993a1835e.png",
|
||||||
"sha256": "ab337b3a2e5c05406c03aa4a69018191624f18de70081accf740e79993a1835e",
|
"sha256": "ab337b3a2e5c05406c03aa4a69018191624f18de70081accf740e79993a1835e",
|
||||||
@@ -80,7 +80,7 @@
|
|||||||
"width": 1024,
|
"width": 1024,
|
||||||
"height": 1536
|
"height": 1536
|
||||||
},
|
},
|
||||||
"apps/waterfall-wiki/seed/characters/reference/01-basket.png": {
|
"apps/dashboard/seed/characters/reference/01-basket.png": {
|
||||||
"id": "48ff5ce0-2763-5fa5-86cb-86c654bf4e24",
|
"id": "48ff5ce0-2763-5fa5-86cb-86c654bf4e24",
|
||||||
"key": "repository-media/sha256/62e6785bb373676cb5f24d44b9fa71a8ec8f413a4d518419aa29006ae62b7af8.png",
|
"key": "repository-media/sha256/62e6785bb373676cb5f24d44b9fa71a8ec8f413a4d518419aa29006ae62b7af8.png",
|
||||||
"sha256": "62e6785bb373676cb5f24d44b9fa71a8ec8f413a4d518419aa29006ae62b7af8",
|
"sha256": "62e6785bb373676cb5f24d44b9fa71a8ec8f413a4d518419aa29006ae62b7af8",
|
||||||
@@ -89,7 +89,7 @@
|
|||||||
"width": 1106,
|
"width": 1106,
|
||||||
"height": 1422
|
"height": 1422
|
||||||
},
|
},
|
||||||
"apps/waterfall-wiki/seed/characters/reference/02-phone.png": {
|
"apps/dashboard/seed/characters/reference/02-phone.png": {
|
||||||
"id": "d21179b3-29aa-57c4-bc50-79f2459b5ee8",
|
"id": "d21179b3-29aa-57c4-bc50-79f2459b5ee8",
|
||||||
"key": "repository-media/sha256/f27657c347e73786cc84df290f689f11284bb57822415a651c4c7567c15d034e.png",
|
"key": "repository-media/sha256/f27657c347e73786cc84df290f689f11284bb57822415a651c4c7567c15d034e.png",
|
||||||
"sha256": "f27657c347e73786cc84df290f689f11284bb57822415a651c4c7567c15d034e",
|
"sha256": "f27657c347e73786cc84df290f689f11284bb57822415a651c4c7567c15d034e",
|
||||||
@@ -98,7 +98,7 @@
|
|||||||
"width": 1161,
|
"width": 1161,
|
||||||
"height": 1354
|
"height": 1354
|
||||||
},
|
},
|
||||||
"apps/waterfall-wiki/seed/characters/reference/03-coffee.png": {
|
"apps/dashboard/seed/characters/reference/03-coffee.png": {
|
||||||
"id": "10e3d787-2575-52f5-8403-cb62072cd1f0",
|
"id": "10e3d787-2575-52f5-8403-cb62072cd1f0",
|
||||||
"key": "repository-media/sha256/cd53d3d1771c014cb4c5c1b7ed4d1846f72ec996d48a889514268461ed9c5f7c.png",
|
"key": "repository-media/sha256/cd53d3d1771c014cb4c5c1b7ed4d1846f72ec996d48a889514268461ed9c5f7c.png",
|
||||||
"sha256": "cd53d3d1771c014cb4c5c1b7ed4d1846f72ec996d48a889514268461ed9c5f7c",
|
"sha256": "cd53d3d1771c014cb4c5c1b7ed4d1846f72ec996d48a889514268461ed9c5f7c",
|
||||||
@@ -107,7 +107,7 @@
|
|||||||
"width": 1191,
|
"width": 1191,
|
||||||
"height": 1320
|
"height": 1320
|
||||||
},
|
},
|
||||||
"apps/waterfall-wiki/seed/characters/reference/04-red-coat.png": {
|
"apps/dashboard/seed/characters/reference/04-red-coat.png": {
|
||||||
"id": "3fe8aac2-160f-5069-9b67-6615732fe54f",
|
"id": "3fe8aac2-160f-5069-9b67-6615732fe54f",
|
||||||
"key": "repository-media/sha256/39249450ccd7f0a207f191742e2c27117f8404d35dfbe092ccdaa54ae59e218d.png",
|
"key": "repository-media/sha256/39249450ccd7f0a207f191742e2c27117f8404d35dfbe092ccdaa54ae59e218d.png",
|
||||||
"sha256": "39249450ccd7f0a207f191742e2c27117f8404d35dfbe092ccdaa54ae59e218d",
|
"sha256": "39249450ccd7f0a207f191742e2c27117f8404d35dfbe092ccdaa54ae59e218d",
|
||||||
@@ -116,7 +116,7 @@
|
|||||||
"width": 1024,
|
"width": 1024,
|
||||||
"height": 1536
|
"height": 1536
|
||||||
},
|
},
|
||||||
"apps/waterfall-wiki/seed/characters/reference/05-blue-dogs.png": {
|
"apps/dashboard/seed/characters/reference/05-blue-dogs.png": {
|
||||||
"id": "8db94ff7-cee3-526d-a594-282a757317cf",
|
"id": "8db94ff7-cee3-526d-a594-282a757317cf",
|
||||||
"key": "repository-media/sha256/d6c7ce1c8e3375f8aaa40012a91d11925113a7776b0fe383c74c734d91edfc37.png",
|
"key": "repository-media/sha256/d6c7ce1c8e3375f8aaa40012a91d11925113a7776b0fe383c74c734d91edfc37.png",
|
||||||
"sha256": "d6c7ce1c8e3375f8aaa40012a91d11925113a7776b0fe383c74c734d91edfc37",
|
"sha256": "d6c7ce1c8e3375f8aaa40012a91d11925113a7776b0fe383c74c734d91edfc37",
|
||||||
@@ -125,7 +125,7 @@
|
|||||||
"width": 1536,
|
"width": 1536,
|
||||||
"height": 1024
|
"height": 1024
|
||||||
},
|
},
|
||||||
"apps/waterfall-wiki/seed/characters/reference/06-dark-dog.png": {
|
"apps/dashboard/seed/characters/reference/06-dark-dog.png": {
|
||||||
"id": "a3a00127-0f67-5c8f-9ae9-259e9c390be0",
|
"id": "a3a00127-0f67-5c8f-9ae9-259e9c390be0",
|
||||||
"key": "repository-media/sha256/9e19ac9d04c30e5d0a552c7d59a8365ffca8be5df1b7f9ec12fb35bef7aa444b.png",
|
"key": "repository-media/sha256/9e19ac9d04c30e5d0a552c7d59a8365ffca8be5df1b7f9ec12fb35bef7aa444b.png",
|
||||||
"sha256": "9e19ac9d04c30e5d0a552c7d59a8365ffca8be5df1b7f9ec12fb35bef7aa444b",
|
"sha256": "9e19ac9d04c30e5d0a552c7d59a8365ffca8be5df1b7f9ec12fb35bef7aa444b",
|
||||||
@@ -134,7 +134,7 @@
|
|||||||
"width": 1372,
|
"width": 1372,
|
||||||
"height": 1146
|
"height": 1146
|
||||||
},
|
},
|
||||||
"apps/waterfall-wiki/seed/characters/reference/07-pockets.png": {
|
"apps/dashboard/seed/characters/reference/07-pockets.png": {
|
||||||
"id": "468c8637-c841-5819-8afb-24d246b77821",
|
"id": "468c8637-c841-5819-8afb-24d246b77821",
|
||||||
"key": "repository-media/sha256/158c1f3a492a7a3223bf4ed56d3750d192c696a054dc66d2c3ae65ce9e0a8dad.png",
|
"key": "repository-media/sha256/158c1f3a492a7a3223bf4ed56d3750d192c696a054dc66d2c3ae65ce9e0a8dad.png",
|
||||||
"sha256": "158c1f3a492a7a3223bf4ed56d3750d192c696a054dc66d2c3ae65ce9e0a8dad",
|
"sha256": "158c1f3a492a7a3223bf4ed56d3750d192c696a054dc66d2c3ae65ce9e0a8dad",
|
||||||
@@ -143,7 +143,7 @@
|
|||||||
"width": 1024,
|
"width": 1024,
|
||||||
"height": 1536
|
"height": 1536
|
||||||
},
|
},
|
||||||
"apps/waterfall-wiki/seed/characters/reference/08-blue-board.png": {
|
"apps/dashboard/seed/characters/reference/08-blue-board.png": {
|
||||||
"id": "1731746a-2a59-5b8b-b91c-8f862567f7ef",
|
"id": "1731746a-2a59-5b8b-b91c-8f862567f7ef",
|
||||||
"key": "repository-media/sha256/fa8f0dd989cb8133ad5565c19dc9f798a9c2bd216e3c90342d1a4e45329e8cd9.png",
|
"key": "repository-media/sha256/fa8f0dd989cb8133ad5565c19dc9f798a9c2bd216e3c90342d1a4e45329e8cd9.png",
|
||||||
"sha256": "fa8f0dd989cb8133ad5565c19dc9f798a9c2bd216e3c90342d1a4e45329e8cd9",
|
"sha256": "fa8f0dd989cb8133ad5565c19dc9f798a9c2bd216e3c90342d1a4e45329e8cd9",
|
||||||
@@ -152,7 +152,7 @@
|
|||||||
"width": 1130,
|
"width": 1130,
|
||||||
"height": 1392
|
"height": 1392
|
||||||
},
|
},
|
||||||
"apps/waterfall-wiki/seed/characters/reference/09-scarf-dog.png": {
|
"apps/dashboard/seed/characters/reference/09-scarf-dog.png": {
|
||||||
"id": "7e99a693-1c95-5544-af4c-ae2ce12824c8",
|
"id": "7e99a693-1c95-5544-af4c-ae2ce12824c8",
|
||||||
"key": "repository-media/sha256/8ee2ea052e4985a28eef1ba415190bdbea70e4ca52eb3bdcd5206210a4285bc7.png",
|
"key": "repository-media/sha256/8ee2ea052e4985a28eef1ba415190bdbea70e4ca52eb3bdcd5206210a4285bc7.png",
|
||||||
"sha256": "8ee2ea052e4985a28eef1ba415190bdbea70e4ca52eb3bdcd5206210a4285bc7",
|
"sha256": "8ee2ea052e4985a28eef1ba415190bdbea70e4ca52eb3bdcd5206210a4285bc7",
|
||||||
@@ -161,7 +161,7 @@
|
|||||||
"width": 1220,
|
"width": 1220,
|
||||||
"height": 1289
|
"height": 1289
|
||||||
},
|
},
|
||||||
"apps/waterfall-wiki/seed/characters/reference/10-elder.png": {
|
"apps/dashboard/seed/characters/reference/10-elder.png": {
|
||||||
"id": "be125dda-5a86-5a7b-9498-a8dba66df31e",
|
"id": "be125dda-5a86-5a7b-9498-a8dba66df31e",
|
||||||
"key": "repository-media/sha256/45bfe1e35f88e52adfc7f97b0834a5dbeea7dbcf283ebdabb9adf4236eb93eab.png",
|
"key": "repository-media/sha256/45bfe1e35f88e52adfc7f97b0834a5dbeea7dbcf283ebdabb9adf4236eb93eab.png",
|
||||||
"sha256": "45bfe1e35f88e52adfc7f97b0834a5dbeea7dbcf283ebdabb9adf4236eb93eab",
|
"sha256": "45bfe1e35f88e52adfc7f97b0834a5dbeea7dbcf283ebdabb9adf4236eb93eab",
|
||||||
@@ -170,7 +170,7 @@
|
|||||||
"width": 1195,
|
"width": 1195,
|
||||||
"height": 1316
|
"height": 1316
|
||||||
},
|
},
|
||||||
"apps/waterfall-wiki/seed/characters/reference/11-long-coat.png": {
|
"apps/dashboard/seed/characters/reference/11-long-coat.png": {
|
||||||
"id": "5bb056a0-735e-5f24-bfd1-1036ebb208e8",
|
"id": "5bb056a0-735e-5f24-bfd1-1036ebb208e8",
|
||||||
"key": "repository-media/sha256/76f00ba08e5b8e7caf4fe2801368cfc5f7cf3c445ae2a80b496a4f431272d053.png",
|
"key": "repository-media/sha256/76f00ba08e5b8e7caf4fe2801368cfc5f7cf3c445ae2a80b496a4f431272d053.png",
|
||||||
"sha256": "76f00ba08e5b8e7caf4fe2801368cfc5f7cf3c445ae2a80b496a4f431272d053",
|
"sha256": "76f00ba08e5b8e7caf4fe2801368cfc5f7cf3c445ae2a80b496a4f431272d053",
|
||||||
@@ -179,7 +179,7 @@
|
|||||||
"width": 1148,
|
"width": 1148,
|
||||||
"height": 1371
|
"height": 1371
|
||||||
},
|
},
|
||||||
"apps/waterfall-wiki/seed/characters/reference/12-trolley.png": {
|
"apps/dashboard/seed/characters/reference/12-trolley.png": {
|
||||||
"id": "11da89bb-daa9-59b3-b22f-997f1fba46fe",
|
"id": "11da89bb-daa9-59b3-b22f-997f1fba46fe",
|
||||||
"key": "repository-media/sha256/8235f95071abb54f0618b61d66c20e63633e6d929a8ec4d43d4cef121226e078.png",
|
"key": "repository-media/sha256/8235f95071abb54f0618b61d66c20e63633e6d929a8ec4d43d4cef121226e078.png",
|
||||||
"sha256": "8235f95071abb54f0618b61d66c20e63633e6d929a8ec4d43d4cef121226e078",
|
"sha256": "8235f95071abb54f0618b61d66c20e63633e6d929a8ec4d43d4cef121226e078",
|
||||||
@@ -188,7 +188,7 @@
|
|||||||
"width": 1241,
|
"width": 1241,
|
||||||
"height": 1267
|
"height": 1267
|
||||||
},
|
},
|
||||||
"apps/waterfall-wiki/seed/characters/reference/13-bucket.png": {
|
"apps/dashboard/seed/characters/reference/13-bucket.png": {
|
||||||
"id": "3f203043-3d69-52c4-91fa-c898daf61eb6",
|
"id": "3f203043-3d69-52c4-91fa-c898daf61eb6",
|
||||||
"key": "repository-media/sha256/2a4b34afdb45d439f2588095abc89378f7772fd842bdc38518e4a03756b15b82.png",
|
"key": "repository-media/sha256/2a4b34afdb45d439f2588095abc89378f7772fd842bdc38518e4a03756b15b82.png",
|
||||||
"sha256": "2a4b34afdb45d439f2588095abc89378f7772fd842bdc38518e4a03756b15b82",
|
"sha256": "2a4b34afdb45d439f2588095abc89378f7772fd842bdc38518e4a03756b15b82",
|
||||||
@@ -197,7 +197,7 @@
|
|||||||
"width": 1140,
|
"width": 1140,
|
||||||
"height": 1380
|
"height": 1380
|
||||||
},
|
},
|
||||||
"apps/waterfall-wiki/seed/characters/reference/14-curly.png": {
|
"apps/dashboard/seed/characters/reference/14-curly.png": {
|
||||||
"id": "5cfb45ab-542f-5bd8-89f5-549072e4caf3",
|
"id": "5cfb45ab-542f-5bd8-89f5-549072e4caf3",
|
||||||
"key": "repository-media/sha256/f10faf8145e56830c9a179b6a52cfdd688de3f460fa33ded82fa2b3fe0678b3e.png",
|
"key": "repository-media/sha256/f10faf8145e56830c9a179b6a52cfdd688de3f460fa33ded82fa2b3fe0678b3e.png",
|
||||||
"sha256": "f10faf8145e56830c9a179b6a52cfdd688de3f460fa33ded82fa2b3fe0678b3e",
|
"sha256": "f10faf8145e56830c9a179b6a52cfdd688de3f460fa33ded82fa2b3fe0678b3e",
|
||||||
@@ -206,7 +206,7 @@
|
|||||||
"width": 1141,
|
"width": 1141,
|
||||||
"height": 1379
|
"height": 1379
|
||||||
},
|
},
|
||||||
"apps/waterfall-wiki/seed/characters/reference/15-red-walk.png": {
|
"apps/dashboard/seed/characters/reference/15-red-walk.png": {
|
||||||
"id": "41e92c94-adca-54b4-9fe9-db192a266937",
|
"id": "41e92c94-adca-54b4-9fe9-db192a266937",
|
||||||
"key": "repository-media/sha256/238eae73ac2faf53e6ac02591b00759115378233fbcbfdcf8e67ce93a804d26e.png",
|
"key": "repository-media/sha256/238eae73ac2faf53e6ac02591b00759115378233fbcbfdcf8e67ce93a804d26e.png",
|
||||||
"sha256": "238eae73ac2faf53e6ac02591b00759115378233fbcbfdcf8e67ce93a804d26e",
|
"sha256": "238eae73ac2faf53e6ac02591b00759115378233fbcbfdcf8e67ce93a804d26e",
|
||||||
@@ -14,6 +14,6 @@ const pending=new Map<string,Promise<Buffer>>();const cache=join(tmpdir(),'promp
|
|||||||
export async function compressPreview(input:Buffer,width:number){const max=width<=480?100_000:220_000;let size=width,quality=72;let output:Buffer;do{output=await sharp(input,{limitInputPixels:40_000_000,animated:false}).rotate().resize({width:size,withoutEnlargement:true}).webp({quality,effort:3}).toBuffer();if(output.length<=max)return output;size=Math.max(240,Math.floor(size*.8));quality=Math.max(30,quality-10);}while(size>240||quality>30);return sharp(input,{limitInputPixels:40_000_000}).rotate().resize({width:240,withoutEnlargement:true}).webp({quality:30}).toBuffer();}
|
export async function compressPreview(input:Buffer,width:number){const max=width<=480?100_000:220_000;let size=width,quality=72;let output:Buffer;do{output=await sharp(input,{limitInputPixels:40_000_000,animated:false}).rotate().resize({width:size,withoutEnlargement:true}).webp({quality,effort:3}).toBuffer();if(output.length<=max)return output;size=Math.max(240,Math.floor(size*.8));quality=Math.max(30,quality-10);}while(size>240||quality>30);return sharp(input,{limitInputPixels:40_000_000}).rotate().resize({width:240,withoutEnlargement:true}).webp({quality:30}).toBuffer();}
|
||||||
export async function cachedPreview(identity:string,width:number,load:()=>Promise<Buffer>,quality=false){const key=createHash('sha256').update(identity+':'+width+(quality?':export-v1':':webp-v1')).digest('hex');const file=join(cache,key+'.webp');try{return {bytes:await readFile(file),etag:'"'+key+'"'};}catch{/* cache miss */}let task=pending.get(key);if(!task){task=limited(async()=>{await mkdir(cache,{recursive:true});const input=await load();const bytes=quality?await sharp(input,{limitInputPixels:40_000_000,animated:false}).rotate().resize({width,withoutEnlargement:true}).webp({quality:95,effort:4}).toBuffer():await compressPreview(input,width);const temp=file+'.'+process.pid+'.tmp';await writeFile(temp,bytes);await rename(temp,file);if(++writes%32===0){const files=await readdir(cache);if(files.length>1024){const dates=await Promise.all(files.map(async name=>({name,time:(await stat(join(cache,name)).catch(()=>({mtimeMs:0}))).mtimeMs})));await Promise.all(dates.sort((a,b)=>a.time-b.time).slice(0,files.length-1024).map(f=>unlink(join(cache,f.name)).catch(()=>{})));}}return bytes;});pending.set(key,task);}try{return {bytes:await task,etag:'"'+key+'"'};}finally{pending.delete(key);}}
|
export async function cachedPreview(identity:string,width:number,load:()=>Promise<Buffer>,quality=false){const key=createHash('sha256').update(identity+':'+width+(quality?':export-v1':':webp-v1')).digest('hex');const file=join(cache,key+'.webp');try{return {bytes:await readFile(file),etag:'"'+key+'"'};}catch{/* cache miss */}let task=pending.get(key);if(!task){task=limited(async()=>{await mkdir(cache,{recursive:true});const input=await load();const bytes=quality?await sharp(input,{limitInputPixels:40_000_000,animated:false}).rotate().resize({width,withoutEnlargement:true}).webp({quality:95,effort:4}).toBuffer():await compressPreview(input,width);const temp=file+'.'+process.pid+'.tmp';await writeFile(temp,bytes);await rename(temp,file);if(++writes%32===0){const files=await readdir(cache);if(files.length>1024){const dates=await Promise.all(files.map(async name=>({name,time:(await stat(join(cache,name)).catch(()=>({mtimeMs:0}))).mtimeMs})));await Promise.all(dates.sort((a,b)=>a.time-b.time).slice(0,files.length-1024).map(f=>unlink(join(cache,f.name)).catch(()=>{})));}}return bytes;});pending.set(key,task);}try{return {bytes:await task,etag:'"'+key+'"'};}finally{pending.delete(key);}}
|
||||||
export async function loadStaticPreview(source:string){
|
export async function loadStaticPreview(source:string){
|
||||||
const asset=(managedMedia as Record<string,{key:string;sha256:string;bytes:number}>)["apps/waterfall-wiki/public"+source];
|
const asset=(managedMedia as Record<string,{key:string;sha256:string;bytes:number}>)["apps/dashboard/public"+source];
|
||||||
if(asset){return {identity:asset.key,load:async()=>{const object=await getObject(asset.key);if(asset.bytes>64*1024*1024)throw Error("Image too large");const bytes=Buffer.from(await object.Body!.transformToByteArray());if(bytes.length!==asset.bytes||createHash("sha256").update(bytes).digest("hex")!==asset.sha256)throw Error("Stored image checksum mismatch");return bytes;}};}
|
if(asset){return {identity:asset.key,load:async()=>{const object=await getObject(asset.key);if(asset.bytes>64*1024*1024)throw Error("Image too large");const bytes=Buffer.from(await object.Body!.transformToByteArray());if(bytes.length!==asset.bytes||createHash("sha256").update(bytes).digest("hex")!==asset.sha256)throw Error("Stored image checksum mismatch");return bytes;}};}
|
||||||
const root=await realpath(join(process.cwd(),'public'));const file=await realpath(staticPreviewPath(source,root));if(!file.startsWith(root+sep))throw Error('Invalid source');const info=await stat(file);if(info.size>64*1024*1024)throw Error('Image too large');return {identity:file+':'+info.mtimeMs,load:()=>readFile(file)};}
|
const root=await realpath(join(process.cwd(),'public'));const file=await realpath(staticPreviewPath(source,root));if(!file.startsWith(root+sep))throw Error('Invalid source');const info=await stat(file);if(info.size>64*1024*1024)throw Error('Image too large');return {identity:file+':'+info.mtimeMs,load:()=>readFile(file)};}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user