Communitygithub.com

app-platform-publish

将网站或应用发布到 DigitalOcean App Platform 并返回其公共 URL。当用户要求发布、部署、上线你所构建的项目时使用此技能。代替需要 Docker 并且在此环境中无法工作的 `publish` 技能 / `apps` 工具。涵盖将代码推送到 GitHub、创建 App Platform 应用、等待部署以及从构建日志中修复失败的过程。

app-platform-publish 是什么?

此技能用于将 Web 应用或静态网站一键部署到 DigitalOcean App Platform。它通过共享凭据(无需用户个人账户)自动完成从代码提交到 GitHub、创建 App Platform 资源、等待部署完成并返回公共 URL 的完整流程。支持静态站点(如 Vite、CRA 构建产物)和带服务器的服务(如 Node.js、Python)两种部署模式,并自动处理常见问题,如环境变量、启动命令和构建失败。部署前会向用户清晰呈现计划(包括成本、公开范围等),每次变更(推送、修改配置、删除)都需要用户单独确认,确保安全可控。整个流程无需 Docker、doctl 等额外工具,仅依赖 curl、git 和 Node.js 环境。

兼容平台~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/jnaidu-do/qm-appplatform-skills/tree/main/app-platform-publish

在你喜欢的 AI 中提问

打开一个已预加载此 Agent Skill 的新对话。

文档

发布到 App Platform

流程:在沙盒中构建并测试 → 使其可部署 → 提出计划并获得同意 → 推送到 GitHub → 创建 App Platform 应用 → 等待 ACTIVE → 检查 URL。

在用户批准步骤 2 中的计划之前,切勿创建仓库、推送或创建/更新/删除应用。 像“发布它”这样的请求并不构成批准。在沙盒中构建和测试无需批准。

绝不要使用 apps 操作的 publish、docker 或 doctl——它们在此计算机上均不能工作。所有操作都通过两个共享凭据进行,你通过核心的凭据代理调用它们。你永远不会看到令牌。

每条命令都要使用一个明确的 sandbox_id,并且对于任何调用代理(digitalocean、github 或 github-git)的命令,请将凭据放在 execute.credentials 中。核心系统仅向这些命令提供 AGENT_CREDENTIAL_TOKEN。不要使用 rm -rf;改用 mktemp -d 创建一个新目录。

计算机环境

Node 24 + npm、Python 3.12 + pip、git、gh(未登录;不要使用它)、curl、gcc、make。没有 docker、doctl、jq、wget、unzip。使用 node -e 或 python3 -c 解析 JSON。

凭据(每种凭据能做什么)

凭据主机允许的操作
digitaloceanapi.digitalocean.comGET/POST/PUT/DELETE 到 /v2/apps 和 /v2/regions
githubapi.github.comGET/POST/PATCH 到 /user 和 /repos/jnaidu-do
github-gitgithub.com (git push/clone)GET/POST 到 /jnaidu-do/

所有内容都在共享的 GitHub 账户 jnaidu-do 下创建(这是一个跨部署的凭据,不是用户自己的账户)。在计划中要说明这一点。如果用户想要自己的账户,请告知他们尚未设置。如果收到 403 path_not_allowed / method_not_allowed,意味着请求超出了此范围——不要尝试变通;告知用户此操作不被允许。

Broker 辅助函数

每条命令粘贴一次(shell 状态不会持久化)。它将 HTTP 状态输出到 stderr,响应体输出到 stdout:

bk() { # bk <凭据> <方法> <url> [json-主体]
  p=$(node -e 'const [c,m,u,b]=process.argv.slice(1);console.log(JSON.stringify({credential:c,method:m,url:u,headers:{accept:"application/json","content-type":"application/json"},...(b?{body:b}:{})}))' "$1" "$2" "$3" "${4:-}")
  curl -fsS -X POST "$AGENT_API_URL/v1/credentials/broker" -H "x-agent-capability: $AGENT_CREDENTIAL_TOKEN" \
    -H "content-type: application/json" -d "$p" |
  node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>{const r=JSON.parse(s);console.error("HTTP "+r.status);console.log(r.body)})'
}

1. 使其可部署(推送前检查)

应用将作为普通容器在 App Platform 中运行,而非此沙盒环境。

  • 监听 $PORT,绑定到 0.0.0.0(App Platform 会设置 PORT;默认为 8080)。切勿硬编码其他端口。
  • 纯 HTML/CSS/JS 或构建为静态文件的框架(Vite、CRA、Astro):部署为 静态站点(build_command、output_dir)。对于静态页面,优先选择此方式而非运行服务器。
  • 任何带服务器的应用:一个 服务,包含 build_command、run_command、http_port。标准布局无需配置即可工作:Node 需要带有 start 脚本的 package.json;Python 需要 requirements.txt 和一个运行命令(例如 gunicorn app:app --bind 0.0.0.0:$PORT)。仓库中的 Dockerfile 也可工作(设置 dockerfile_path),但你不能在此处测试构建它,所以请保持简单。
  • 无持久磁盘。 写入文件系统的任何数据在每次部署后都会丢失。不要使用 SQLite 文件、上传到磁盘的文件或 JSON 文件来存储状态。如果应用需要数据库,请添加托管的 Postgres(见下文);否则保持无状态。
  • 先在本地运行:npm install,用 PORT=8080 启动,然后 curl localhost:8080。仅发布能工作的内容。
  • 添加包含 node_modules 等的 .gitignore。不要提交密钥。切勿打印令牌。

如果应用使用 Postgres

App Platform 的 ${db.DATABASE_URL} 包含 sslmode=require,而 Node pg 会拒绝托管的 CA(SELF_SIGNED_CERT_IN_CHAIN)。从 URL 中移除 sslmode 并且 设置 ssl: { rejectUnauthorized: false }:

function normalizeDatabaseUrl(raw) {
  const u = new URL(raw);
  u.searchParams.delete("sslmode"); u.searchParams.delete("ssl");
  return u.toString();
}
const pool = new Pool({ connectionString: normalizeDatabaseUrl(process.env.DATABASE_URL), ssl: { rejectUnauthorized: false } });

在规范中添加 "databases": [{"name":"db","engine":"PG","production":false,"version":"16"}],并在每个需要它的组件中添加 {"key":"DATABASE_URL","scope":"RUN_TIME","value":"${db.DATABASE_URL}"}。首次连接重试约 30 秒:数据库可能仍在启动。仅当应用确实需要数据库时才添加(这会产生额外费用)。

2. 提出计划并征询(在此处停止,直到他们同意)

一旦应用构建并本地运行成功,停下来并向用户发送简短计划,然后等待回复。 在用户批准之前,不要执行任何 bk 调用、git push 或仓库/应用创建。包括:

  1. 你构建的内容 以及它已在本地运行(一行)。
  2. 你将采取的步骤,按顺序排列,每一步说明其涉及的内容:
    • 创建 GitHub 仓库 jnaidu-do/<名称> — 公开,位于共享的 jnaidu-do 账户下(不是用户自己的账户)
    • 使用 git 将应用文件推送到该仓库(列出包含的内容;确认没有密钥)
    • 在 nyc 区域创建 App Platform 应用 <名称>:静态站点(少量免费)或服务(basic-xxs,约 $5/月)——说明是哪一种及费用
    • 等待构建(3-5 分钟),然后提供公共 URL
  3. 哪些内容变为公开: 仓库和实时站点对互联网上的任何人都可读。
  4. 未经再次询问你不会做什么: 推送进一步更改、编辑规范或删除任何内容。

以一个直接的问题结尾,例如:“我应该按照上述步骤创建仓库和应用吗?您也可以更改名称、使仓库私有(这需要授予 DO GitHub App 权限)或拒绝。” 如果适用,提供备选方案(静态 vs 服务、不同名称)。如果他们拒绝,交给他们文件或已准备就绪可提交的代码,然后停止。

在以下情况之前,单独再次询问:任何后续的 git push(更新)、更改应用规范、以及删除应用。

3. 推送到 GitHub

使用你在步骤 2 中提议的名称(qm-<短横线连接的名>)(小写)。如果名称已被占用(422),则添加一个短后缀。

# 创建仓库(公开:App Platform 无需额外认证即可克隆)
bk github POST https://api.github.com/user/repos '{"name":"qm-hello-site","private":false}'

# 从一个包含应用的全新目录推送(没有 node_modules)
cd "$(mktemp -d)" && cp -r /path/to/app/. . && git init -q -b main && git add . &&
git -c user.name=QM -c [email protected] commit -qm "Initial commit" &&
git remote add origin "$AGENT_API_URL/v1/credentials/git/github-git/jnaidu-do/qm-hello-site.git" &&
git -c http.extraHeader="x-agent-capability: $AGENT_CREDENTIAL_TOKEN" push origin HEAD:refs/heads/main

后续更新:以相同方式提交并推送(HEAD:refs/heads/main),但仅在你告知用户更改内容并获得同意之后——每次推送都需要一个新的批准,不包含在最初的计划中。

4. 创建 App Platform 应用

bk digitalocean POST https://api.digitalocean.com/v2/apps '{"spec": {
  "name": "qm-hello-site",
  "region": "nyc",
  "services": [{
    "name": "web",
    "github": {"repo": "jnaidu-do/qm-hello-site", "branch": "main", "deploy_on_push": true},
    "build_command": "npm install",
    "run_command": "npm start",
    "http_port": 8080,
    "instance_size_slug": "basic-xxs",
    "instance_count": 1
  }]
}}'

静态站点替代 services:"static_sites": [{"name":"web","github":{...},"build_command":"npm run build","output_dir":"dist"}](纯 HTML:省略 build_command,output_dir 为 "/";对于单页应用,添加 "catchall_document":"index.html")。

响应中包含应用 id。请保留它。应用名称只能包含小写字母、数字、连字符,长度为 2-32 个字符。

如果创建失败并出现 GitHub 仓库访问错误(“does not have access to the repository”):DigitalOcean GitHub App 未被授予此仓库的权限。告知用户打开 https://github.com/apps/digitalocean/installations/select_target 并授予对 jnaidu-do 仓库的访问权限,然后告知完成;重新尝试相同的请求。如果他们无法做到,回退到通用的公共克隆源("git": {"repo_clone_url": "https://github.com/jnaidu-do/qm-hello-site.git", "branch": "main"})并告知他们后续更新需要重新部署(见下文)。

5. 等待 ACTIVE

真实构建需要 3-5 分钟。每 30 秒轮询一次,并告知用户你正在等待:

bk digitalocean GET "https://api.digitalocean.com/v2/apps/$APP_ID/deployments?per_page=1" | node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>{const d=JSON.parse(s).deployments[0];console.log(d.id,d.phase,d.progress?.success_steps+"/"+d.progress?.total_steps)})'

阶段:PENDING_BUILD → BUILDING → DEPLOYING → ACTIVE。ERROR 或 CANCELED 表示失败;转到“失败时”。在大约 12 分钟后停止轮询,并告知用户当前状态。

6. 报告 URL

bk digitalocean GET "https://api.digitalocean.com/v2/apps/$APP_ID" | node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>console.log(JSON.parse(s).app.live_url))'

尝试 curl -s -o /dev/null -w '%{http_code}' <live_url> — 期望得到 200。此计算机通常无法访问新的 *.ondigitalocean.app 站点(连接失败)。如果 curl 失败但部署状态是 ACTIVE,那不是部署失败:告知用户你无法从此处验证,请用户打开该 URL。将完整的 https://…ondigitalocean.app URL 和仓库 URL 提供给用户。应用在互联网上是 公开 的。

失败时

自行读取日志;不要向用户索要。获取日志 URL,然后获取日志:

bk digitalocean GET "https://api.digitalocean.com/v2/apps/$APP_ID/deployments/$DEPLOYMENT_ID/logs?type=BUILD"
# 响应体包含 "live_url"(或 "historic_urls");curl 该 URL(无需认证)

type 是 BUILD、DEPLOY 或 RUN(静态站点只有 BUILD)。匹配信号:

信号修复方法
Missing script: start / 找不到命令build_command / run_command 错误,或者没有 start 脚本
容器退出,健康检查失败未监听 $PORT / 未绑定到 0.0.0.0,或者启动时崩溃
SELF_SIGNED_CERT_IN_CHAIN缺少 Postgres SSL 辅助函数(见上文)
Cannot find module依赖项在 package.json 中缺失,或提交的 node_modules 不匹配
Vite "Blocked request. This host is not allowed"设置 preview: { allowedHosts: true }(或将构建的 dist 作为静态站点提供服务)
构建 "skipped" / 几秒内进入 ACTIVE源代码未更改;推送一个真正的提交

告知用户根本原因(确切的日志行)和你提议的修复,在推送修复或更改规范前需要征询——每次推送和每次规范更改都需要单独的批准。用户批准后,修复代码、提交并推送(deploy_on_push 会自动重新部署),然后再次轮询。对于规范更改,发送 PUT /v2/apps/$APP_ID 并携带 {"spec": {...}}(首先使用 GET /v2/apps/$APP_ID 获取当前规范并编辑它)。对于通用的 git 源,使用 POST /v2/apps/$APP_ID/deployments 并携带 {"force_build":true} 来强制重新构建。不要通过删除和重新创建应用来修复失败——这会改变 URL。向前修复。在三次不同的失败尝试后放弃,并报告你已经尝试过的内容。读取日志无需批准。

删除

仅在用户明确要求时,并且先说出应用名称。DELETE /v2/apps/$APP_ID 删除应用。GitHub 仓库会被保留(此凭据无法删除仓库);请告知用户。

报告

结束时,提供:实时 URL、仓库 URL,以及一行关于如何更新的说明(“请求我更改 X,我将推送它”)。

相关技能