feat: deploy Pascal from vendored source
This commit is contained in:
parent
40ff3ca851
commit
5260502507
2
.gitignore
vendored
2
.gitignore
vendored
@ -87,7 +87,7 @@ checkpoints/
|
|||||||
# 第三方参考源码(不提交到git)
|
# 第三方参考源码(不提交到git)
|
||||||
reference-sources/
|
reference-sources/
|
||||||
|
|
||||||
# Local self-hosted Pascal Editor checkout.
|
# Legacy external Pascal Editor checkout (the managed source is under services/).
|
||||||
.pascal-editor/
|
.pascal-editor/
|
||||||
|
|
||||||
# Drizzle migrations must be versioned (override the global *.sql ignore above)
|
# Drizzle migrations must be versioned (override the global *.sql ignore above)
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
/* include utility classes in streamdown */
|
/* include utility classes in streamdown */
|
||||||
@source '../node_modules/streamdown/dist/index.js';
|
@source '../node_modules/streamdown/dist/index.js';
|
||||||
|
@source not '../services/pascal-editor';
|
||||||
|
|
||||||
/* custom variant for setting dark mode programmatically */
|
/* custom variant for setting dark mode programmatically */
|
||||||
@custom-variant dark (&:is(.dark, .dark *));
|
@custom-variant dark (&:is(.dark, .dark *));
|
||||||
|
|||||||
@ -13,6 +13,7 @@
|
|||||||
"!lib/db/migrations/meta",
|
"!lib/db/migrations/meta",
|
||||||
"!public/vendor",
|
"!public/vendor",
|
||||||
"!public/three",
|
"!public/three",
|
||||||
|
"!services/pascal-editor",
|
||||||
"!vercel.json"
|
"!vercel.json"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|||||||
@ -2,10 +2,11 @@
|
|||||||
|
|
||||||
## Current Shape
|
## Current Shape
|
||||||
|
|
||||||
The app now exposes a first-phase Pascal Editor workspace at `/pascal-editor`.
|
The app exposes a Pascal Editor workspace at `/pascal-editor`. Its customized
|
||||||
It is intentionally iframe-based so Pascal can be self-hosted as a standalone
|
source is versioned with the main repository under `services/pascal-editor`,
|
||||||
Next app without forcing its unpublished `@pascal-app/nodes` workspace package
|
while it remains an independently built Next app embedded through an iframe.
|
||||||
into the main application dependency graph.
|
This keeps Pascal's Bun workspace and `@pascal-app/nodes` dependency graph out
|
||||||
|
of the host application's pnpm dependency graph.
|
||||||
|
|
||||||
The wrapper route is protected by the host application's existing login system.
|
The wrapper route is protected by the host application's existing login system.
|
||||||
Unauthenticated users are redirected to `/login?callbackUrl=/pascal-editor`.
|
Unauthenticated users are redirected to `/login?callbackUrl=/pascal-editor`.
|
||||||
@ -26,11 +27,10 @@ loading a broken iframe.
|
|||||||
## Why Iframe First
|
## Why Iframe First
|
||||||
|
|
||||||
`@pascal-app/core`, `@pascal-app/viewer`, and `@pascal-app/editor` are published
|
`@pascal-app/core`, `@pascal-app/viewer`, and `@pascal-app/editor` are published
|
||||||
on npm, but the official standalone app still depends on `@pascal-app/nodes`
|
on npm, but the standalone app also depends on its monorepo workspace packages.
|
||||||
from its monorepo workspace. That package registers the built-in node
|
Keeping the complete Pascal source tree below `services/pascal-editor` preserves
|
||||||
definitions used by walls, doors, windows, levels, roofs, stairs, and other
|
the built-in walls, doors, windows, levels, roofs, stairs, MCP tools, and our AI
|
||||||
editable building elements. Until it is published or vendored, a pure npm-based
|
Agent customization without coupling the host build to those packages.
|
||||||
native React integration would be incomplete.
|
|
||||||
|
|
||||||
## Deployment Options
|
## Deployment Options
|
||||||
|
|
||||||
@ -42,9 +42,9 @@ Run Pascal Editor as a local standalone service:
|
|||||||
pnpm pascal:dev
|
pnpm pascal:dev
|
||||||
```
|
```
|
||||||
|
|
||||||
The helper clones `pascalorg/editor` into the ignored `.pascal-editor/`
|
The helper uses `services/pascal-editor`, installs its dependencies when needed,
|
||||||
directory on first run, installs its dependencies, and starts `apps/editor` on
|
and starts `apps/editor` on `http://localhost:3002`. Set `PASCAL_EDITOR_DIR` only
|
||||||
`http://localhost:3002`.
|
when deliberately testing a different Pascal checkout.
|
||||||
|
|
||||||
Then open:
|
Then open:
|
||||||
|
|
||||||
@ -121,13 +121,20 @@ For repeatable deployment and upgrades on additional servers, use
|
|||||||
variables, health check, real-model smoke test, and rollback behavior are
|
variables, health check, real-model smoke test, and rollback behavior are
|
||||||
documented in `docs/pascal-production-deployment.md`.
|
documented in `docs/pascal-production-deployment.md`.
|
||||||
|
|
||||||
## Next Step
|
## Source Maintenance
|
||||||
|
|
||||||
After the iframe POC is stable, the native integration path is:
|
`services/pascal-editor` was imported with Git subtree so its upstream history
|
||||||
|
can still be refreshed deliberately. Apply and test upstream updates in a
|
||||||
|
dedicated branch before merging them with our localization, host authentication,
|
||||||
|
scene storage, and AI Agent changes:
|
||||||
|
|
||||||
1. Pin a Pascal upstream commit.
|
```bash
|
||||||
2. Vendor or workspace-link `packages/nodes` as `@pascal-app/nodes`.
|
git subtree pull \
|
||||||
3. Add Pascal packages to `transpilePackages`.
|
--prefix=services/pascal-editor \
|
||||||
4. Upgrade `three` to the Pascal-compatible `^0.184` line and retest existing
|
https://github.com/pascalorg/editor.git main \
|
||||||
3D/CAD surfaces.
|
--squash
|
||||||
5. Wire `Editor` `onLoad` / `onSave` to our project storage APIs.
|
```
|
||||||
|
|
||||||
|
Production servers do not run this command. They only pull the reviewed main
|
||||||
|
repository and let `pnpm pascal:deploy` sync the vendored source into the
|
||||||
|
isolated runtime directory.
|
||||||
|
|||||||
@ -1,32 +1,30 @@
|
|||||||
# Pascal Editor 可复用生产部署
|
# Pascal Editor 可复用生产部署
|
||||||
|
|
||||||
本项目把 Pascal Editor 作为独立 Bun/Next.js 服务运行,并通过主站 Nginx
|
Pascal Editor 的定制源码位于主仓库 `services/pascal-editor`,以独立
|
||||||
同域挂载。部署脚本会复用主站登录态、系统设置中显式标记的“多模态模型”和主站
|
Bun/Next.js 服务运行,并通过主站 Nginx 同域挂载。新服务器只需拉取
|
||||||
Git 仓库里的 `pascal-editor-ai-agent` 独立分支。
|
主仓库,无需再克隆或维护 Pascal 独立分支。
|
||||||
|
|
||||||
## 默认结构
|
## 默认结构
|
||||||
|
|
||||||
| 项目 | 默认值 |
|
| 项目 | 默认值 |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| 主站目录 | 当前仓库目录 |
|
| 主站源码 | 当前仓库目录 |
|
||||||
|
| Pascal 内置源码 | `services/pascal-editor` |
|
||||||
|
| Pascal 运行副本 | `/opt/pascal-editor` |
|
||||||
|
| Pascal 用户数据 | `/var/lib/pascal-editor` |
|
||||||
| 主站 PM2 服务 | `ai-chatbot` |
|
| 主站 PM2 服务 | `ai-chatbot` |
|
||||||
| 主站内部地址 | `http://127.0.0.1:3125` |
|
|
||||||
| Pascal 源码 | `/opt/pascal-editor` |
|
|
||||||
| Pascal 数据 | `/var/lib/pascal-editor` |
|
|
||||||
| Pascal PM2 服务 | `pascal-editor` |
|
| Pascal PM2 服务 | `pascal-editor` |
|
||||||
| Pascal 监听 | `127.0.0.1:3002` |
|
| Pascal 监听 | `127.0.0.1:3002` |
|
||||||
| 同域路径 | `/pascal-service` |
|
| 同域路径 | `/pascal-service` |
|
||||||
| Pascal Git 分支 | `pascal-editor-ai-agent` |
|
|
||||||
|
|
||||||
Pascal 端口不对公网开放。Nginx 会先通过主站
|
`/opt/pascal-editor` 是部署脚本生成的运行副本,不是第二个源码仓库,
|
||||||
`/api/internal/pascal/access` 校验登录 Cookie,再把可信用户 ID 注入 Pascal。
|
不应在该目录直接修改代码。Pascal 端口不对公网开放。Nginx 先通过
|
||||||
场景的创建、列表、读取、保存、删除、事件流和 AI Agent 都检查同一个
|
`/api/internal/pascal/access` 校验主站登录 Cookie,再注入可信用户 ID。
|
||||||
`ownerId`。
|
场景数据按 `ownerId` 隔离。
|
||||||
|
|
||||||
## 首次部署
|
## 首次部署
|
||||||
|
|
||||||
在目标服务器先部署好主站并确认主站 Git remote 能读取
|
目标服务器部署好主站后执行:
|
||||||
`pascal-editor-ai-agent` 分支,然后执行:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd /var/www/ai-chatbot
|
cd /var/www/ai-chatbot
|
||||||
@ -41,39 +39,43 @@ sudo env \
|
|||||||
脚本会按顺序:
|
脚本会按顺序:
|
||||||
|
|
||||||
1. 生成或复用 `PASCAL_PROXY_SECRET`,写入主站 `.env.local`。
|
1. 生成或复用 `PASCAL_PROXY_SECRET`,写入主站 `.env.local`。
|
||||||
2. 构建并重启主站。
|
2. 安装主站开发依赖,构建并重启主站。
|
||||||
3. 安装固定版本 Bun(默认 `1.3.0`)。
|
3. 安装固定版本 Bun(默认 `1.3.0`)和 `rsync`。
|
||||||
4. 克隆或快进 Pascal 独立分支。
|
4. 将 `services/pascal-editor` 同步为独立运行副本。
|
||||||
5. 写入仅 root 可读的 Pascal 生产环境文件。
|
5. 写入仅 root 可读的 Pascal 生产环境文件。
|
||||||
6. 安装依赖、构建 Pascal,并注册 PM2 服务。
|
6. 安装依赖、构建 Pascal,并注册 PM2 服务。
|
||||||
7. 渲染 Nginx 片段、执行 `nginx -t`,成功后热重载。
|
7. 渲染 Nginx 片段,校验成功后热重载。
|
||||||
8. 检查主站、Pascal、Agent 状态和主站模型网关。
|
8. 检查主站、Pascal、Agent 状态和主站模型网关。
|
||||||
|
|
||||||
主站环境文件和 Nginx 配置会备份到 `/var/backups/pascal-deploy`。Nginx
|
主站环境文件、Nginx 配置以及旧 Pascal Git 元数据会备份到
|
||||||
校验失败时,脚本会立即恢复执行前的配置。
|
`/var/backups/pascal-deploy`。Nginx 校验失败时会恢复原配置。
|
||||||
|
|
||||||
## 日常更新
|
## 日常更新
|
||||||
|
|
||||||
主站和 Pascal 分支有新提交后,重复执行同一个命令即可。脚本只允许 Git
|
以后只需更新主仓库,并重复执行部署命令:
|
||||||
快进更新;如果 `/opt/pascal-editor` 有本地修改,会停止部署,不会覆盖现场
|
|
||||||
文件。
|
```bash
|
||||||
|
git pull --ff-only origin main
|
||||||
|
sudo env PASCAL_PUBLIC_ORIGIN=https://example.com pnpm pascal:deploy
|
||||||
|
```
|
||||||
|
|
||||||
|
`rsync --delete` 会更新运行副本,但保留 `/opt/pascal-editor/.env.production`。
|
||||||
|
用户数据始终位于独立的 `PASCAL_DATA_DIR`,不会被代码同步覆盖。
|
||||||
|
|
||||||
只做无模型费用的健康检查:
|
只做无模型费用的健康检查:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo env \
|
sudo env PASCAL_PUBLIC_ORIGIN=https://example.com pnpm pascal:check
|
||||||
PASCAL_PUBLIC_ORIGIN=https://example.com \
|
|
||||||
pnpm pascal:check
|
|
||||||
```
|
```
|
||||||
|
|
||||||
执行一次真实自然语言建模、验证节点已落库并自动删除临时场景:
|
执行一次真实自然语言建模,验证节点落库后删除临时场景:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo env PASCAL_SMOKE_CONFIRM=1 pnpm pascal:smoke
|
sudo env PASCAL_SMOKE_CONFIRM=1 pnpm pascal:smoke
|
||||||
```
|
```
|
||||||
|
|
||||||
真实冒烟会调用当前系统设置中显式标记的“多模态模型”,因此默认不会在一键部署时
|
真实冒烟会调用系统设置中显式标记的“多模态模型”,默认不在一键部署
|
||||||
执行。需要每次部署自动执行时设置 `RUN_AGENT_SMOKE_TEST=true`。
|
时执行。需要每次部署自动执行时设置 `RUN_AGENT_SMOKE_TEST=true`。
|
||||||
|
|
||||||
## 常用覆盖项
|
## 常用覆盖项
|
||||||
|
|
||||||
@ -83,16 +85,15 @@ sudo env PASCAL_SMOKE_CONFIRM=1 pnpm pascal:smoke
|
|||||||
| `MAIN_ENV_FILE` | 主站环境文件 |
|
| `MAIN_ENV_FILE` | 主站环境文件 |
|
||||||
| `MAIN_PM2_NAME` | 主站 PM2 服务名 |
|
| `MAIN_PM2_NAME` | 主站 PM2 服务名 |
|
||||||
| `MAIN_INTERNAL_URL` | Pascal 调用主站网关的地址 |
|
| `MAIN_INTERNAL_URL` | Pascal 调用主站网关的地址 |
|
||||||
| `MAIN_NGINX_UPSTREAM` | Nginx 主站 upstream,例如 `127.0.0.1:3125` |
|
| `MAIN_NGINX_UPSTREAM` | Nginx 主站 upstream |
|
||||||
| `BUILD_MAIN_APP=false` | 跳过主站安装、构建和重启 |
|
| `BUILD_MAIN_APP=false` | 跳过主站安装、构建和重启 |
|
||||||
| `PASCAL_INSTALL_DIR` | Pascal 源码目录 |
|
| `PASCAL_SOURCE_DIR` | Pascal 内置源码目录 |
|
||||||
| `PASCAL_DATA_DIR` | SQLite 数据目录 |
|
| `PASCAL_INSTALL_DIR` | Pascal 运行副本目录 |
|
||||||
| `PASCAL_GIT_REPO` | Pascal 分支所在 Git 仓库;默认使用主站 origin |
|
| `PASCAL_DATA_DIR` | SQLite 用户数据目录 |
|
||||||
| `PASCAL_GIT_REF` | Pascal 分支或标签 |
|
|
||||||
| `PASCAL_PORT` | Pascal 本地监听端口 |
|
| `PASCAL_PORT` | Pascal 本地监听端口 |
|
||||||
| `PASCAL_BASE_PATH` | 同域挂载路径 |
|
| `PASCAL_BASE_PATH` | 同域挂载路径 |
|
||||||
| `PASCAL_PUBLIC_ORIGIN` | 公网主站 Origin,不带末尾斜杠 |
|
| `PASCAL_PUBLIC_ORIGIN` | 公网主站 Origin,不带末尾斜杠 |
|
||||||
| `NGINX_SITE_CONFIG` | 需要加入 Pascal include 的 HTTPS server 配置 |
|
| `NGINX_SITE_CONFIG` | 需加入 Pascal include 的 HTTPS server 配置 |
|
||||||
| `INSTALL_NGINX=false` | 仅部署服务,不修改 Nginx |
|
| `INSTALL_NGINX=false` | 仅部署服务,不修改 Nginx |
|
||||||
| `PASCAL_BUN_VERSION` | 固定 Bun 版本 |
|
| `PASCAL_BUN_VERSION` | 固定 Bun 版本 |
|
||||||
| `PASCAL_BACKUP_DIR` | 环境与 Nginx 配置备份目录 |
|
| `PASCAL_BACKUP_DIR` | 环境与 Nginx 配置备份目录 |
|
||||||
@ -103,9 +104,10 @@ sudo env PASCAL_SMOKE_CONFIRM=1 pnpm pascal:smoke
|
|||||||
pm2 status
|
pm2 status
|
||||||
pm2 logs pascal-editor --lines 100
|
pm2 logs pascal-editor --lines 100
|
||||||
curl http://127.0.0.1:3002/pascal-service/api/health
|
curl http://127.0.0.1:3002/pascal-service/api/health
|
||||||
|
cat /opt/pascal-editor/.pascal-source-version
|
||||||
nginx -t
|
nginx -t
|
||||||
```
|
```
|
||||||
|
|
||||||
升级时不要把主站源码复制到 Pascal 目录,也不要把本地测试数据库上传到
|
不要将本地测试数据库放进 `services/pascal-editor`,也不要直接修改
|
||||||
服务器。主站始终更新 `main`,Pascal 始终更新独立分支,用户场景只保存在
|
`/opt/pascal-editor`。代码只更新主仓 `main`,用户场景只保存在
|
||||||
`PASCAL_DATA_DIR`。
|
`PASCAL_DATA_DIR`。
|
||||||
|
|||||||
@ -12,9 +12,8 @@ MAIN_NGINX_UPSTREAM="${MAIN_NGINX_UPSTREAM:-127.0.0.1:3125}"
|
|||||||
BUILD_MAIN_APP="${BUILD_MAIN_APP:-true}"
|
BUILD_MAIN_APP="${BUILD_MAIN_APP:-true}"
|
||||||
|
|
||||||
PASCAL_INSTALL_DIR="${PASCAL_INSTALL_DIR:-/opt/pascal-editor}"
|
PASCAL_INSTALL_DIR="${PASCAL_INSTALL_DIR:-/opt/pascal-editor}"
|
||||||
|
PASCAL_SOURCE_DIR="${PASCAL_SOURCE_DIR:-$MAIN_APP_DIR/services/pascal-editor}"
|
||||||
PASCAL_DATA_DIR="${PASCAL_DATA_DIR:-/var/lib/pascal-editor}"
|
PASCAL_DATA_DIR="${PASCAL_DATA_DIR:-/var/lib/pascal-editor}"
|
||||||
PASCAL_GIT_REF="${PASCAL_GIT_REF:-pascal-editor-ai-agent}"
|
|
||||||
PASCAL_GIT_REPO="${PASCAL_GIT_REPO:-}"
|
|
||||||
PASCAL_PM2_NAME="${PASCAL_PM2_NAME:-pascal-editor}"
|
PASCAL_PM2_NAME="${PASCAL_PM2_NAME:-pascal-editor}"
|
||||||
PASCAL_PORT="${PASCAL_PORT:-3002}"
|
PASCAL_PORT="${PASCAL_PORT:-3002}"
|
||||||
PASCAL_NGINX_UPSTREAM="${PASCAL_NGINX_UPSTREAM:-127.0.0.1:${PASCAL_PORT}}"
|
PASCAL_NGINX_UPSTREAM="${PASCAL_NGINX_UPSTREAM:-127.0.0.1:${PASCAL_PORT}}"
|
||||||
@ -127,6 +126,17 @@ install_bun() {
|
|||||||
[ -x "$PASCAL_BUN_BIN" ] || die "Bun installation did not create $PASCAL_BUN_BIN"
|
[ -x "$PASCAL_BUN_BIN" ] || die "Bun installation did not create $PASCAL_BUN_BIN"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
install_rsync() {
|
||||||
|
if command -v rsync >/dev/null 2>&1; then
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
[ "$(id -u)" -eq 0 ] || die "Installing rsync requires root; install rsync manually"
|
||||||
|
require_command apt-get
|
||||||
|
apt-get update -qq
|
||||||
|
DEBIAN_FRONTEND=noninteractive apt-get install -y -qq rsync
|
||||||
|
}
|
||||||
|
|
||||||
configure_main_app() {
|
configure_main_app() {
|
||||||
local secret="$1"
|
local secret="$1"
|
||||||
local next_env_backup=""
|
local next_env_backup=""
|
||||||
@ -176,29 +186,40 @@ configure_main_app() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
resolve_pascal_repo() {
|
sync_pascal_source() {
|
||||||
if [ -n "$PASCAL_GIT_REPO" ]; then
|
[ -f "$PASCAL_SOURCE_DIR/package.json" ] ||
|
||||||
printf '%s' "$PASCAL_GIT_REPO"
|
die "Vendored Pascal source not found: $PASCAL_SOURCE_DIR"
|
||||||
return
|
[ -f "$PASCAL_SOURCE_DIR/apps/editor/package.json" ] ||
|
||||||
fi
|
die "Pascal editor package not found below $PASCAL_SOURCE_DIR"
|
||||||
git -C "$MAIN_APP_DIR" remote get-url origin
|
[ "$PASCAL_INSTALL_DIR" != "/" ] || die "PASCAL_INSTALL_DIR cannot be /"
|
||||||
}
|
[ "$PASCAL_INSTALL_DIR" != "$MAIN_APP_DIR" ] ||
|
||||||
|
die "PASCAL_INSTALL_DIR cannot overwrite MAIN_APP_DIR"
|
||||||
|
[ "$PASCAL_INSTALL_DIR" != "$PASCAL_SOURCE_DIR" ] ||
|
||||||
|
die "PASCAL_INSTALL_DIR must be separate from PASCAL_SOURCE_DIR"
|
||||||
|
|
||||||
|
install_rsync
|
||||||
|
mkdir -p "$PASCAL_INSTALL_DIR" "$PASCAL_BACKUP_DIR"
|
||||||
|
|
||||||
checkout_pascal() {
|
|
||||||
local repo="$1"
|
|
||||||
if [ -d "$PASCAL_INSTALL_DIR/.git" ]; then
|
if [ -d "$PASCAL_INSTALL_DIR/.git" ]; then
|
||||||
[ -z "$(git -C "$PASCAL_INSTALL_DIR" status --porcelain)" ] ||
|
[ -z "$(git -C "$PASCAL_INSTALL_DIR" status --porcelain)" ] ||
|
||||||
die "$PASCAL_INSTALL_DIR has local Git changes; refusing to overwrite them"
|
die "$PASCAL_INSTALL_DIR has legacy local Git changes; refusing to overwrite them"
|
||||||
git -C "$PASCAL_INSTALL_DIR" fetch origin "$PASCAL_GIT_REF"
|
local legacy_git_backup="$PASCAL_BACKUP_DIR/legacy-pascal-git-$(date +%Y%m%d%H%M%S)"
|
||||||
git -C "$PASCAL_INSTALL_DIR" checkout "$PASCAL_GIT_REF"
|
log "Archiving legacy Pascal Git metadata to $legacy_git_backup"
|
||||||
git -C "$PASCAL_INSTALL_DIR" pull --ff-only origin "$PASCAL_GIT_REF"
|
mv "$PASCAL_INSTALL_DIR/.git" "$legacy_git_backup"
|
||||||
return
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
[ ! -e "$PASCAL_INSTALL_DIR" ] ||
|
log "Syncing vendored Pascal source from $PASCAL_SOURCE_DIR"
|
||||||
die "$PASCAL_INSTALL_DIR exists but is not a Git checkout"
|
rsync -a --delete \
|
||||||
mkdir -p "$(dirname "$PASCAL_INSTALL_DIR")"
|
--exclude '.env.production' \
|
||||||
git clone --single-branch --branch "$PASCAL_GIT_REF" "$repo" "$PASCAL_INSTALL_DIR"
|
--exclude '.git/' \
|
||||||
|
--exclude '.next/' \
|
||||||
|
--exclude '.turbo/' \
|
||||||
|
--exclude 'node_modules/' \
|
||||||
|
"$PASCAL_SOURCE_DIR/" "$PASCAL_INSTALL_DIR/"
|
||||||
|
|
||||||
|
if git -C "$MAIN_APP_DIR" rev-parse --verify HEAD >/dev/null 2>&1; then
|
||||||
|
git -C "$MAIN_APP_DIR" rev-parse HEAD >"$PASCAL_INSTALL_DIR/.pascal-source-version"
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
write_pascal_env() {
|
write_pascal_env() {
|
||||||
@ -226,7 +247,7 @@ build_pascal() {
|
|||||||
mkdir -p "$PASCAL_DATA_DIR"
|
mkdir -p "$PASCAL_DATA_DIR"
|
||||||
chmod 700 "$PASCAL_DATA_DIR"
|
chmod 700 "$PASCAL_DATA_DIR"
|
||||||
export PATH="$(dirname "$PASCAL_BUN_BIN"):$PATH"
|
export PATH="$(dirname "$PASCAL_BUN_BIN"):$PATH"
|
||||||
log "Installing and building Pascal from $PASCAL_GIT_REF"
|
log "Installing and building vendored Pascal service"
|
||||||
(
|
(
|
||||||
cd "$PASCAL_INSTALL_DIR"
|
cd "$PASCAL_INSTALL_DIR"
|
||||||
"$PASCAL_BUN_BIN" install --frozen-lockfile
|
"$PASCAL_BUN_BIN" install --frozen-lockfile
|
||||||
@ -369,7 +390,7 @@ main() {
|
|||||||
[ "$(id -u)" -eq 0 ] || die "Production deployment must run as root"
|
[ "$(id -u)" -eq 0 ] || die "Production deployment must run as root"
|
||||||
configure_main_app "$secret"
|
configure_main_app "$secret"
|
||||||
install_bun
|
install_bun
|
||||||
checkout_pascal "$(resolve_pascal_repo)"
|
sync_pascal_source
|
||||||
write_pascal_env "$secret"
|
write_pascal_env "$secret"
|
||||||
build_pascal
|
build_pascal
|
||||||
start_pascal
|
start_pascal
|
||||||
|
|||||||
@ -1,22 +1,13 @@
|
|||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
import { spawn, spawnSync } from "node:child_process";
|
import { spawn, spawnSync } from "node:child_process";
|
||||||
import {
|
import { existsSync, writeFileSync } from "node:fs";
|
||||||
copyFileSync,
|
|
||||||
existsSync,
|
|
||||||
mkdirSync,
|
|
||||||
readFileSync,
|
|
||||||
writeFileSync,
|
|
||||||
} from "node:fs";
|
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
|
|
||||||
const cwd = process.cwd();
|
const cwd = process.cwd();
|
||||||
const pascalDir = path.resolve(
|
const pascalDir = path.resolve(
|
||||||
cwd,
|
cwd,
|
||||||
process.env.PASCAL_EDITOR_DIR || ".pascal-editor"
|
process.env.PASCAL_EDITOR_DIR || "services/pascal-editor"
|
||||||
);
|
);
|
||||||
const repoUrl =
|
|
||||||
process.env.PASCAL_EDITOR_REPO || "https://github.com/pascalorg/editor.git";
|
|
||||||
const repoRef = process.env.PASCAL_EDITOR_REF || "main";
|
|
||||||
const port = process.env.PASCAL_EDITOR_PORT || "3002";
|
const port = process.env.PASCAL_EDITOR_PORT || "3002";
|
||||||
|
|
||||||
function run(command, args, options = {}) {
|
function run(command, args, options = {}) {
|
||||||
@ -36,72 +27,6 @@ function commandExists(command) {
|
|||||||
return result.status === 0;
|
return result.status === 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
function patchNestedNextRoot() {
|
|
||||||
const configPath = path.join(pascalDir, "apps/editor/next.config.ts");
|
|
||||||
if (!existsSync(configPath)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let config = readFileSync(configPath, "utf8");
|
|
||||||
if (!config.includes("import path from 'node:path'")) {
|
|
||||||
config = config.replace(
|
|
||||||
"import type { NextConfig } from 'next'\n",
|
|
||||||
"import path from 'node:path'\nimport type { NextConfig } from 'next'\n"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!config.includes("root: path.resolve(process.cwd(), '../..')")) {
|
|
||||||
config = config.replace(
|
|
||||||
" turbopack: {\n",
|
|
||||||
" turbopack: {\n root: path.resolve(process.cwd(), '../..'),\n"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
writeFileSync(configPath, config, "utf8");
|
|
||||||
}
|
|
||||||
|
|
||||||
function ensureSparseCheckout() {
|
|
||||||
run(
|
|
||||||
"git",
|
|
||||||
[
|
|
||||||
"-C",
|
|
||||||
pascalDir,
|
|
||||||
"sparse-checkout",
|
|
||||||
"set",
|
|
||||||
"apps/editor",
|
|
||||||
"packages",
|
|
||||||
"tooling",
|
|
||||||
"styles",
|
|
||||||
],
|
|
||||||
{ cwd }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function patchCssImports() {
|
|
||||||
const source = path.join(pascalDir, "styles/elevation.css");
|
|
||||||
const targetDir = path.join(pascalDir, "apps/editor/styles");
|
|
||||||
const target = path.join(targetDir, "elevation.css");
|
|
||||||
const globalsPath = path.join(pascalDir, "apps/editor/app/globals.css");
|
|
||||||
|
|
||||||
if (existsSync(source)) {
|
|
||||||
mkdirSync(targetDir, { recursive: true });
|
|
||||||
copyFileSync(source, target);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!existsSync(globalsPath)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const globals = readFileSync(globalsPath, "utf8");
|
|
||||||
const patched = globals.replace(
|
|
||||||
'../../../styles/elevation.css',
|
|
||||||
'../styles/elevation.css'
|
|
||||||
);
|
|
||||||
if (patched !== globals) {
|
|
||||||
writeFileSync(globalsPath, patched, "utf8");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function runPackageScript(packageManager, packageDir, scriptName) {
|
function runPackageScript(packageManager, packageDir, scriptName) {
|
||||||
if (packageManager === "bun") {
|
if (packageManager === "bun") {
|
||||||
run("bun", ["--cwd", packageDir, "run", scriptName], { cwd: pascalDir });
|
run("bun", ["--cwd", packageDir, "run", scriptName], { cwd: pascalDir });
|
||||||
@ -127,36 +52,18 @@ function ensureBuiltWorkspacePackages(packageManager) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!existsSync(pascalDir)) {
|
if (!existsSync(path.join(pascalDir, "apps/editor/package.json"))) {
|
||||||
mkdirSync(path.dirname(pascalDir), { recursive: true });
|
|
||||||
run("git", [
|
|
||||||
"clone",
|
|
||||||
"--depth",
|
|
||||||
"1",
|
|
||||||
"--filter=blob:none",
|
|
||||||
"--sparse",
|
|
||||||
"--branch",
|
|
||||||
repoRef,
|
|
||||||
repoUrl,
|
|
||||||
pascalDir,
|
|
||||||
]);
|
|
||||||
} else if (!existsSync(path.join(pascalDir, ".git"))) {
|
|
||||||
console.error(
|
console.error(
|
||||||
`${pascalDir} already exists but is not a git checkout. Move it away or set PASCAL_EDITOR_DIR.`
|
`Vendored Pascal Editor source was not found at ${pascalDir}. Set PASCAL_EDITOR_DIR to override it.`
|
||||||
);
|
);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
ensureSparseCheckout();
|
|
||||||
patchCssImports();
|
|
||||||
|
|
||||||
const envLocalPath = path.join(pascalDir, ".env.local");
|
const envLocalPath = path.join(pascalDir, ".env.local");
|
||||||
if (!existsSync(envLocalPath)) {
|
if (!existsSync(envLocalPath)) {
|
||||||
writeFileSync(envLocalPath, `PORT=${port}\n`, "utf8");
|
writeFileSync(envLocalPath, `PORT=${port}\n`, "utf8");
|
||||||
}
|
}
|
||||||
|
|
||||||
patchNestedNextRoot();
|
|
||||||
|
|
||||||
const hasBun = commandExists("bun");
|
const hasBun = commandExists("bun");
|
||||||
const hasNpm = commandExists("npm");
|
const hasNpm = commandExists("npm");
|
||||||
const hasNodeModules = existsSync(path.join(pascalDir, "node_modules"));
|
const hasNodeModules = existsSync(path.join(pascalDir, "node_modules"));
|
||||||
|
|||||||
19
services/pascal-editor/HOST_INTEGRATION.md
Normal file
19
services/pascal-editor/HOST_INTEGRATION.md
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
# Host Application Integration
|
||||||
|
|
||||||
|
This directory is the source of truth for the Pascal Editor service shipped
|
||||||
|
with the host application. It originated from `pascalorg/editor` and includes
|
||||||
|
the host authentication, user-scoped scene storage, Chinese localization, and
|
||||||
|
AI Agent integration maintained by this project.
|
||||||
|
|
||||||
|
## Boundaries
|
||||||
|
|
||||||
|
- The host application uses pnpm and does not install this workspace as part of
|
||||||
|
its own dependency graph.
|
||||||
|
- Pascal uses its own Bun lockfile and is built as an independent Next service.
|
||||||
|
- Production deploys copy this directory to `/opt/pascal-editor`.
|
||||||
|
- Runtime secrets remain in `/opt/pascal-editor/.env.production`.
|
||||||
|
- User scene data remains outside both source trees in `/var/lib/pascal-editor`.
|
||||||
|
|
||||||
|
Run `pnpm pascal:dev` from the host repository for local development. Run
|
||||||
|
`pnpm pascal:deploy` as root on a production server after pulling the host main
|
||||||
|
branch. Never commit runtime environment files or SQLite scene databases here.
|
||||||
@ -31,5 +31,5 @@
|
|||||||
"next.config.js",
|
"next.config.js",
|
||||||
".next/dev/types/**/*.ts"
|
".next/dev/types/**/*.ts"
|
||||||
],
|
],
|
||||||
"exclude": ["node_modules"]
|
"exclude": ["node_modules", "services/pascal-editor"]
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user