Files
makelore/src/lib/works-publish-helpers.ts
2026-07-29 17:22:35 +08:00

77 lines
7.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

type PublishSourceDeliverable = {
stageId: string;
content: string;
};
export function getWorksPublishDefaultSummary(
projectName: string,
deliverables: PublishSourceDeliverable[],
): string {
const preferredDeliverable = deliverables.find((deliverable) => deliverable.stageId === 'marketing')
?? deliverables.find((deliverable) => deliverable.stageId === 'pm')
?? deliverables[0];
const sourceText = preferredDeliverable?.content
.replace(/<[^>]*>/g, ' ')
.replace(/[#*_`>|[\]()-]+/g, ' ')
.replace(/\s+/g, ' ')
.trim();
if (sourceText) {
return sourceText.slice(0, 180);
}
return `${projectName} 的 Makelore 课程作品。`;
}
export function getDeployPackageZipPath(
deliverables: PublishSourceDeliverable[],
stageId = 'deploy',
): string | null {
const deployDeliverables = deliverables.filter((deliverable) => deliverable.stageId === stageId);
for (let index = deployDeliverables.length - 1; index >= 0; index -= 1) {
const match = deployDeliverables[index].content.match(/(?:[A-Za-z]:[\\/][^\s`'",。;;<>]+|\/[^\s`'",。;;<>]+)\.zip\b/i);
if (match?.[0]) return match[0];
}
return null;
}
export function buildDeployProductBuildPrompt(input: {
projectName: string;
projectPath: string;
zipPath: string | null;
agentName?: string;
}): string {
const agentName = input.agentName?.trim() || '部署工程师';
return [
`请作为${agentName},直接开始构建当前项目的可发布产品文件。`,
`项目名称:${input.projectName}`,
`项目根目录:${input.projectPath}`,
'处理过程交互规则:',
'1. 先自己检查项目类型、启动方式和已有文件,能根据项目事实判断的就直接做。',
'2. 只有遇到会改变产品公开信息、需要用户选择、或需要权限确认的动作时,才在聊天页提问。',
'3. 需要运行命令或修改文件时,按工具权限流程处理;每完成一个关键步骤,用 1-2 句大白话同步进度。',
'4. 不要让用户填写作品 ID、标题、简介、分类、年龄段、难度、版本名、更新说明或 zip 路径,这些都由你根据项目内容写入文件。',
'构建要求:',
'1. 确认或创建 `Dockerfile`。',
'2. 确认或创建 `docker-compose.yml`。公开服务进程必须监听容器内 `0.0.0.0:8080`,端口映射使用 `127.0.0.1::8080` 动态宿主机端口。',
'3. 确认或创建 `niancode.yml`,使用 `runtime: compose`、`compose.file: docker-compose.yml`、`compose.public_service` 和 `compose.public_port: 8080`。',
'4. 把当前项目打成 `.zip` 包。zip 根目录必须直接包含 `niancode.yml` 和 `docker-compose.yml`,不要再套一层项目目录。',
'5. 打包时排除 `.git/`、`node_modules/`、`.venv/`、`venv/`、`.next/cache/`、`.pytest_cache/`、`.env`、证书、私钥和本地缓存。',
'6. 先执行完整发布验收:结构化解析 `niancode.yml` 和 `docker-compose.yml`,检查 build context、Dockerfile、端口、容器内 `0.0.0.0:8080` 监听、volume、运行命令、锁文件、Vite 相对资源、沙箱存储、密钥、环境变量、ZIP 路径/大小/数量和缓存排除项。只允许安全默认值 `${NAME:-safe-default}`;真实 `.env*` 只能排除,`.env.example` 只能放无敏感示例。',
'7. 如果本机有 Docker 和浏览器,执行完整 smoke:`docker compose config`、`docker compose build --pull=false`、`docker compose up -d`、容器状态、`docker port 8080/tcp`、HTTP smoke、Vite 入口 HTML 资源路径、日志和清理;然后在全新空目录解压 ZIP 重复整套 smoke。如果本机没有 Docker 或浏览器,不要假装 PASS,也不要停在“等用户去服务器启动”:把对应动态检查写成 `status: BLOCKED`、`execution: unavailable`,在 detail/部署报告中说明“本机环境不可用,交由 Works Square 云端构建验证”,继续生成 ZIP、works-publish.json 和交接报告。',
'8. 在不带 `allow-same-origin` 的 sandbox iframe 中做浏览器 smoke;任何白屏、未捕获 `SecurityError` 或启动失败都必须判定为 BLOCKED。若项目是游戏或画布项目,额外覆盖桌面视口、移动视口和一次 resize/全屏变化,确认根节点占满 iframe、主画布按逻辑分辨率保持最大等比尺寸;任一维度比预期小超过 5% 就是 BLOCKED。若浏览器不可用,同样用 `execution: unavailable` 记录事实,不要伪造画布尺寸证据。',
'9. 创建或更新项目根目录 `works-publish.json`,让 Main 的云端部署协调器读取它。',
' 必填字段:app_id、title、summary、category、age_band、difficulty、version_name、change_log、zip_file_path。',
' zip_file_path 必须指向已经生成的 `.zip` 包。',
'10. 在项目根目录创建 `works-deploy-check.json`。它必须是 UTF-8 JSON,schema_version 为 1,包含 status、checked_at、zip_file_path、zip_sha256,以及 compose_config、compose_build、compose_up、container_port、http_smoke、fresh_directory_smoke、sandbox_browser_smoke 这些 checks;每项都要有 PASS、SKIPPED、BLOCKED 之一以及 detail、command,动态检查还要标记 `execution: executed` 或 `execution: unavailable`。实际成功才写 PASS;Docker 或浏览器自动化不可用时写 SKIPPED 或 BLOCKED + execution: unavailable;命令执行失败或项目有问题写 BLOCKED。游戏或画布项目还必须增加 `game_canvas_smoke` 和尺寸证据。总状态优先级为 BLOCKED > SKIPPED > PASS;报告路径不一致或哈希不一致仍然 BLOCKED,云端只允许把明确标记 unavailable 的本地动态检查交给 Works Square 复核;哈希不一致仍然 BLOCKED。',
'11. 输出 `部署报告.md`,逐项列出 ZIP 路径、大小、解压大小、文件数、根目录前 40 项、公开服务/8080、容器内 `0.0.0.0:8080`、每个 build context/Dockerfile、无 bind mount、禁止项、Compose build/up、容器状态、docker port、HTTP 状态码、Vite base、入口 HTML、Web Storage/Cookie/IndexedDB/Service Worker、sandbox smoke;游戏或画布项目再列出逻辑分辨率、桌面/移动视口、主画布实际尺寸、最大等比预期尺寸和 resize 复测;最后写 PASS、SKIPPED 或 BLOCKED。',
'交接规则:',
'1. 当前任务如果由“部署与上传”页面的“构建产品”触发,Main 已经登记一次云端部署意图;你只需要完成文件、ZIP 和报告,Main 会在交接文件就绪且当前登录态有效时自动把 ZIP 提交到 Works Square 的远端构建接口。',
'2. 不要让用户去服务器手动启动 Docker,也不要询问 SSH 主机、用户名、端口或目标路径;本机 Docker/浏览器不可用不是让用户接管部署的理由。',
'3. 不能把“已生成文件”“已提交远端”“远端构建完成”“已公开”混为一谈。只有看到 `works-cloud-deploy.json` 的 `status: submitted` 和真实 `version_id` 才能说已提交远端;queued/building/reviewing 仍然不是已公开。',
'4. 如果当前会话没有云端部署意图文件,完成交接文件后说明需要从“部署与上传”页面点击“构建产品”来授权一次云端提交;不要自行调用未知服务器或暴露令牌。',
'5. 如果当前会话不能读取登录态或访问令牌,不要卡住构建;保留 `works-publish.json`、`works-deploy-check.json` 和 zip 包,让 Main 在登录态恢复后自动继续,并在报告里写明等待登录。',
input.zipPath
? `已检测到旧部署报告里的 zip 包路径:${input.zipPath},请核对后决定是否复用或重新生成。`
: '如果还没有 zip 包,请先生成;完成静态安全检查和真实可执行的动态 smoke 后,把最终路径写入 works-publish.json。若动态环境不可用,必须明确标记 SKIPPED 或 BLOCKED + execution: unavailable 并让 Main 的云端复核接管,不得伪造 PASS;仅环境原因的 SKIPPED 必须提示未验证风险。',
].join('\n');
}