@clawos-dev/clawd 0.2.619 → 0.2.621
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app-builder-plugin/mcp-server.cjs +79 -582
- package/dist/cli.cjs +313 -1720
- package/dist/dispatch/mcp-server.cjs +79 -595
- package/dist/persona-defaults/persona-app-builder/.claude/skills/app-builder-projects/scripts/collect-files.mjs +6 -15
- package/dist/persona-defaults/persona-app-builder/.claude/skills/app-builder-projects/scripts/list-projects.mjs +1 -2
- package/dist/persona-defaults/persona-app-builder/.claude/skills/app-builder-projects/scripts/record-push.mjs +3 -5
- package/dist/persona-defaults/persona-app-builder/.claude/skills/app-builder-projects/scripts/render-sql.mjs +4 -6
- package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/server/src/app.module.ts +1 -7
- package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/server/src/main.ts +3 -9
- package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/server/src/polyfill.ts +3 -9
- package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/server/src/table-name.ts +2 -5
- package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/web/src/App.jsx +1 -8
- package/dist/persona-defaults/persona-app-builder/mcp/clawd-app-builder/deploy-kit/scripts/ensure-credentials.sh +10 -47
- package/dist/persona-defaults/persona-app-builder/mcp/clawd-app-builder/deploy-kit/scripts/ensure-toolchain.sh +3 -17
- package/dist/persona-defaults/persona-app-builder/mcp/clawd-app-builder/deploy-kit/scripts/new-extension.sh +8 -21
- package/dist/persona-defaults/persona-app-builder/mcp/clawd-app-builder/deploy-kit/scripts/publish.sh +14 -67
- package/dist/persona-defaults/persona-app-builder/mcp/clawd-app-builder/deploy-kit/scripts/remove-extension.sh +6 -24
- package/dist/persona-defaults/persona-app-builder/mcp/clawd-app-builder/deploy-kit/scripts/verify.sh +3 -15
- package/dist/persona-defaults/persona-app-builder/mcp/clawd-app-builder/mcp-server.cjs +79 -582
- package/dist/persona-defaults/persona-clawd-butler/.claude/skills/clawd-session-import/scripts/link-sessions.mjs +7 -23
- package/dist/persona-defaults/persona-clawd-butler/.claude/skills/scan-sessions-suggest-personas/scripts/extract-signals.mjs +6 -14
- package/dist/persona-defaults/persona-dataclaw-builder/.claude/skills/app-builder-projects/scripts/collect-files.mjs +6 -15
- package/dist/persona-defaults/persona-dataclaw-builder/.claude/skills/app-builder-projects/scripts/list-projects.mjs +1 -2
- package/dist/persona-defaults/persona-dataclaw-builder/.claude/skills/app-builder-projects/scripts/record-push.mjs +3 -5
- package/dist/persona-defaults/persona-dataclaw-builder/.claude/skills/app-builder-projects/scripts/render-sql.mjs +4 -6
- package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/examples/nestjs-react/server/src/app.module.ts +2 -6
- package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/examples/nestjs-react/server/src/dataclaw/dataclaw.client.ts +2 -4
- package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/examples/nestjs-react/server/src/dataclaw/dataclaw.controller.ts +1 -2
- package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/examples/nestjs-react/server/src/main.ts +4 -11
- package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/examples/nestjs-react/server/src/polyfill.ts +1 -5
- package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/examples/nestjs-react/web/src/api.js +1 -4
- package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/examples/nestjs-react/web/src/components/BarChart.jsx +0 -1
- package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/examples/nestjs-react/web/src/components/ResultCards.jsx +0 -1
- package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/examples/nestjs-react/web/src/components/ResultTable.jsx +0 -1
- package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/examples/nestjs-react/web/src/enums.js +2 -3
- package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/hooks/pre-deploy.sh +0 -2
- package/dist/persona-defaults/persona-dataclaw-builder/mcp/clawd-app-builder/deploy-kit/scripts/ensure-credentials.sh +10 -47
- package/dist/persona-defaults/persona-dataclaw-builder/mcp/clawd-app-builder/deploy-kit/scripts/ensure-toolchain.sh +3 -17
- package/dist/persona-defaults/persona-dataclaw-builder/mcp/clawd-app-builder/deploy-kit/scripts/new-extension.sh +8 -21
- package/dist/persona-defaults/persona-dataclaw-builder/mcp/clawd-app-builder/deploy-kit/scripts/publish.sh +14 -67
- package/dist/persona-defaults/persona-dataclaw-builder/mcp/clawd-app-builder/deploy-kit/scripts/remove-extension.sh +6 -24
- package/dist/persona-defaults/persona-dataclaw-builder/mcp/clawd-app-builder/deploy-kit/scripts/verify.sh +3 -15
- package/dist/persona-defaults/persona-dataclaw-builder/mcp/clawd-app-builder/mcp-server.cjs +79 -582
- package/dist/pi-mcp-bridge/extension.cjs +0 -1
- package/package.json +1 -1
|
@@ -1,17 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
// + 永远排掉 `.git` 和 `.clawd-project.json`(插件的记账文件:publish / record-push 都会改它,进了仓库或哈希就会
|
|
6
|
-
// 「每推一次都变一次」,而且里面的 deployments / pushedCommit 也不是源码)。**不要**在这里另写一份排除表:publish.sh 渲进项目目录的 s.int.yaml / s.prod.yaml 含 supabase key,
|
|
7
|
-
// 它进不进仓库只该由 .gitignore 一处决定。
|
|
8
|
-
// 二进制(前 8KB 含 NUL)和超过 512KB 的文件跳过并列进 `skipped`——push_files 只收文本,
|
|
9
|
-
// 而且每个字节都要经模型上下文走一遍;agent 看到 skipped 里有该进仓库的东西要自己判断。
|
|
10
|
-
//
|
|
2
|
+
// 收集 GitHub MCP push_files 的文本文件;过滤以项目 .gitignore 为准,避免 s.*.yaml 中的 key 被提交。
|
|
3
|
+
// 始终排除 .git 及根 .clawd-project.json,记账变化不属于源码,不能进入推送或树哈希。
|
|
4
|
+
// 二进制(前 8KB 含 NUL)及超过 512KB 的文件列入 skipped,不进哈希;调用方需判断是否另行处理。
|
|
11
5
|
// 用法:collect-files.mjs --project <项目目录绝对路径>
|
|
12
6
|
// 输出:{ files: [{ path, content }], skipped: [{ path, reason }], treeHash }
|
|
13
|
-
//
|
|
14
|
-
// publish tool 再跑一次本脚本比对——不一致就是「改了没推」,拒绝发布。skipped 里的文件不进哈希。
|
|
7
|
+
// treeHash 为按 path 排序的 sha256(path\0content\0…);publish 重算后与 record-push 记账比较,拒绝未推的改动。
|
|
15
8
|
import { createHash } from 'node:crypto'
|
|
16
9
|
import { readdir, readFile, stat } from 'node:fs/promises'
|
|
17
10
|
import { join, relative, sep } from 'node:path'
|
|
@@ -58,7 +51,6 @@ function ignored(rel, isDir, rules) {
|
|
|
58
51
|
const segments = rel.split('/')
|
|
59
52
|
for (const rule of rules) {
|
|
60
53
|
if (rule.anchored) {
|
|
61
|
-
// 前缀匹配:rel 等于它、或在它下面
|
|
62
54
|
for (let i = 1; i <= segments.length; i++) {
|
|
63
55
|
const head = segments.slice(0, i).join('/')
|
|
64
56
|
if (rule.re.test(head) && (!rule.dirOnly || isDir || i < segments.length)) return true
|
|
@@ -67,7 +59,6 @@ function ignored(rel, isDir, rules) {
|
|
|
67
59
|
}
|
|
68
60
|
for (let i = 0; i < segments.length; i++) {
|
|
69
61
|
if (!rule.re.test(segments[i])) continue
|
|
70
|
-
// dirOnly:命中的这一段得是目录——要么它不是最后一段(后面还有东西),要么 rel 本身就是目录
|
|
71
62
|
if (!rule.dirOnly || i < segments.length - 1 || isDir) return true
|
|
72
63
|
}
|
|
73
64
|
}
|
|
@@ -78,7 +69,7 @@ let rules = []
|
|
|
78
69
|
try {
|
|
79
70
|
rules = parseGitignore(await readFile(join(root, '.gitignore'), 'utf8'))
|
|
80
71
|
} catch {
|
|
81
|
-
//
|
|
72
|
+
// 无法读取 .gitignore 时仍排除 .git 和根 .clawd-project.json。
|
|
82
73
|
}
|
|
83
74
|
|
|
84
75
|
const files = []
|
|
@@ -109,7 +100,7 @@ async function walk(dir) {
|
|
|
109
100
|
}
|
|
110
101
|
|
|
111
102
|
await walk(root)
|
|
112
|
-
//
|
|
103
|
+
// 按字符串序而非 localeCompare 排序,避免不同机器 locale 改变树哈希。
|
|
113
104
|
files.sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0))
|
|
114
105
|
const hash = createHash('sha256')
|
|
115
106
|
for (const f of files) hash.update(f.path).update('\0').update(f.content).update('\0')
|
|
@@ -24,8 +24,7 @@ for (const dirName of names) {
|
|
|
24
24
|
try {
|
|
25
25
|
const value = JSON.parse(await readFile(join(projectsRoot, dirName, '.clawd-project.json'), 'utf8'))
|
|
26
26
|
if (value?.name !== dirName || typeof value.createdAt !== 'string') continue
|
|
27
|
-
//
|
|
28
|
-
// 插件还没读写过的老文件顶层就有 prodUrl,原样带出。
|
|
27
|
+
// prodUrl 兼容字段优先取 deployments.prod.url,缺席时保留 legacy 顶层值。
|
|
29
28
|
const prodUrl = value.deployments?.prod?.url
|
|
30
29
|
projects.push({ ...value, ...(prodUrl ? { prodUrl } : {}), projectDir: join(projectsRoot, dirName) })
|
|
31
30
|
} catch {
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
// 用法:record-push.mjs --project <项目目录绝对路径> --commit <push_files 返回的 object.sha> --tree-hash <collect-files.mjs 输出的 treeHash> [--repo-url <https://github.com/ottin4ttc/...>]
|
|
2
|
+
// GitHub MCP push 后记 repoUrl、pushedCommit 和 pushedTreeHash 到 .clawd-project.json。
|
|
3
|
+
// 无本地 git 的项目发布时重算树哈希;一致才以 pushedCommit 记 publishedCommit。
|
|
4
|
+
// 用法:record-push.mjs --project <项目目录绝对路径> --commit <object.sha> --tree-hash <treeHash> [--repo-url <https://github.com/ottin4ttc/...>]
|
|
7
5
|
// 输出:写回后的元数据 JSON
|
|
8
6
|
import { readFile, writeFile } from 'node:fs/promises'
|
|
9
7
|
import { join } from 'node:path'
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
// integration → <APP_NAME>_<SLUG>_int_ prod → <APP_NAME>_<SLUG>_
|
|
6
|
-
// ext.conf 没 SLUG 的老项目退成 <APP_NAME>_int_ / <APP_NAME>_。
|
|
2
|
+
// 渲 server/db/schema.sql 的 __TABLE_PREFIX__ 到 stdout,共用一份 DDL。
|
|
3
|
+
// 前缀与 publish/remove 一致:integration 为 <APP_NAME>_<SLUG>_int_,prod 为 <APP_NAME>_<SLUG>_。
|
|
4
|
+
// legacy 无 SLUG 时分别退为 <APP_NAME>_int_ / <APP_NAME>_。
|
|
7
5
|
import { readFile } from 'node:fs/promises'
|
|
8
6
|
import { join } from 'node:path'
|
|
9
7
|
|
|
@@ -27,7 +25,7 @@ function fail(msg) {
|
|
|
27
25
|
process.exit(1)
|
|
28
26
|
}
|
|
29
27
|
|
|
30
|
-
/**
|
|
28
|
+
/** 只解析单行 KEY=value 及单/双引号,不执行 ext.conf 的 shell 内容。 */
|
|
31
29
|
function readConf(text) {
|
|
32
30
|
const out = {}
|
|
33
31
|
for (const line of text.split('\n')) {
|
|
@@ -1,12 +1,6 @@
|
|
|
1
1
|
import { Module } from '@nestjs/common';
|
|
2
2
|
|
|
3
|
-
//
|
|
4
|
-
// 历史背景:之前导入 MessagesModule 自带留言板 API + Supabase repo,用户起 dev server 时 nest
|
|
5
|
-
// 启动会初始化 supabase 模块(控制台 noise + 旁边业务代码会暗示 assistant 沿用留言板风格)。
|
|
6
|
-
// 瘦身后只留启动骨架,业务逻辑由 assistant 按用户需求从零加(添 controller/module 进 imports 即可)。
|
|
7
|
-
//
|
|
8
|
-
// vite middleware / 静态资源 serve 路径仍由 main.ts 处理(dev mount vite,prod express.static),
|
|
9
|
-
// 不需要 ServeStaticModule。
|
|
3
|
+
// 留空启动骨架,由项目按需求注册业务模块;静态资源由 main.ts 托管,无需 ServeStaticModule。
|
|
10
4
|
@Module({
|
|
11
5
|
imports: [],
|
|
12
6
|
})
|
|
@@ -5,11 +5,8 @@ import { AppModule } from './app.module';
|
|
|
5
5
|
import { TABLE_PREFIX } from './table-name';
|
|
6
6
|
import * as path from 'node:path';
|
|
7
7
|
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
// /api 走 web/vite.config.js 的 proxy 转到本进程)
|
|
11
|
-
// - prod / FC:nest 直接 serve web 的打包产物(publish.sh 的 BUILD_CMD 把 web/dist 拷进 dist/public)
|
|
12
|
-
// FC_SERVER_PORT 是 FC 运行时注入的端口;PORT 给本地调试。
|
|
8
|
+
// dev 由 Vite 独跑并代理 /api;FC 托管拷入 dist/public 的 web 产物。
|
|
9
|
+
// 端口优先 FC_SERVER_PORT,其次本地 PORT。
|
|
13
10
|
async function bootstrap() {
|
|
14
11
|
const app = await NestFactory.create<NestExpressApplication>(AppModule);
|
|
15
12
|
|
|
@@ -18,10 +15,7 @@ async function bootstrap() {
|
|
|
18
15
|
|
|
19
16
|
app.setGlobalPrefix('api', { exclude: ['/'] });
|
|
20
17
|
|
|
21
|
-
//
|
|
22
|
-
// 1. FC(CODE_DIR=./server 只上传 server/,运行时 __dirname=dist/):publish.sh BUILD_CMD 末尾
|
|
23
|
-
// 把 ../web/dist 拷进 dist/public,跟着 server 一起上传,所以优先用 __dirname/public
|
|
24
|
-
// 2. 本地 `node dist/main.js`:__dirname/public 不存在时 fallback 到 ../../web/dist
|
|
18
|
+
// FC 仅上传 server/,优先托管 dist/public;本地构建可回落到 web/dist。
|
|
25
19
|
const fs = await import('node:fs');
|
|
26
20
|
const fcStaticDir = path.resolve(__dirname, 'public');
|
|
27
21
|
const monorepoStaticDir = path.resolve(__dirname, '..', '..', 'web', 'dist');
|
|
@@ -1,14 +1,8 @@
|
|
|
1
|
-
//
|
|
2
|
-
// server 拿不到 SUPABASE_URL/KEY → createClient 跑了但 process.env 是空 → PGRST 系列错码。
|
|
3
|
-
// 这里在 nest bootstrap 之前显式 require dotenv 把 .env 填进 process.env。
|
|
4
|
-
// prod 路径(node --env-file=.env dist/main.js)Node 自带 loader 已经填了,dotenv 看到
|
|
5
|
-
// 已存在的 env 不会覆盖(默认行为),行为对齐。
|
|
1
|
+
// nest watch 不经过 Node --env-file;bootstrap 前加载 .env,dotenv 不覆盖已注入的 env。
|
|
6
2
|
require('dotenv/config');
|
|
7
3
|
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
// (FC 运行时给的 node 可能是 18/20)下都能正常 createClient。
|
|
11
|
-
// 注意:不要用 `WebSocket` 作为局部变量名,会与 DOM lib 的全局类型重声明冲突。
|
|
4
|
+
// Supabase Realtime 初始化需要 WebSocket;缺原生实现时(如 Node 18/20)用 ws 补全。
|
|
5
|
+
// 局部构造器避免命名为 WebSocket,以免与 DOM 全局类型冲突。
|
|
12
6
|
const WsCtor = require('ws');
|
|
13
7
|
if (!(globalThis as any).WebSocket) {
|
|
14
8
|
(globalThis as any).WebSocket = WsCtor;
|
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
// Supabase
|
|
2
|
-
//
|
|
3
|
-
// 线上由 publish 按 env 注进 FC 的环境变量。代码里凡碰表的地方都走 tableName('visits'),不写死前缀。
|
|
4
|
-
// 表结构写在 server/db/schema.sql(同样用 __TABLE_PREFIX__ 占位),见那边注释。
|
|
1
|
+
// Supabase 表名统一为 TABLE_PREFIX + 基础名,按 integration / prod 隔离。
|
|
2
|
+
// prefix 从 dev .env 或 FC env 注入;DDL 使用同源 __TABLE_PREFIX__ 占位。
|
|
5
3
|
|
|
6
4
|
const prefix = process.env.TABLE_PREFIX;
|
|
7
5
|
if (!prefix) {
|
|
@@ -13,7 +11,6 @@ if (!prefix) {
|
|
|
13
11
|
|
|
14
12
|
export const TABLE_PREFIX: string = prefix;
|
|
15
13
|
|
|
16
|
-
/** `tableName('visits')` → `<APP_NAME>_<SLUG>_int_visits`(integration)/ `<APP_NAME>_<SLUG>_visits`(prod) */
|
|
17
14
|
export function tableName(base: string): string {
|
|
18
15
|
return `${TABLE_PREFIX}${base}`;
|
|
19
16
|
}
|
|
@@ -1,11 +1,4 @@
|
|
|
1
|
-
//
|
|
2
|
-
// 不会闪一下 demo 内容再被 assistant 改成用户实际想要的页面。
|
|
3
|
-
//
|
|
4
|
-
// 历史背景:之前模板自带留言板 demo(App.jsx + messages controller),用户从「新建项目 →
|
|
5
|
-
// 起 dev server → 假设 assistant 改 App.jsx 成 hello world」整个流程里第一眼看到的是留言板
|
|
6
|
-
// UI,HMR 替换后才变成真正的内容 —— 视觉闪动 + 业务代码被模板暗示带偏(spec follow-up
|
|
7
|
-
// 2026-06-03 老板拍)。瘦身后骨架只保留 vite + nest + react 可启动结构,内容由 assistant
|
|
8
|
-
// 按用户需求从零写。
|
|
1
|
+
// 空白起手页,避免模板 demo 暗示业务方向;内容由项目按需求补充。
|
|
9
2
|
export default function App() {
|
|
10
3
|
return <div />;
|
|
11
4
|
}
|
|
@@ -1,30 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
#
|
|
3
|
-
#
|
|
4
|
-
# 被 `source` 进来,再调 ensure_credentials。跟 ensure-toolchain.sh 是一对:
|
|
5
|
-
# 那个管「命令在不在」,这个管「命令拿什么身份跑」。
|
|
6
|
-
#
|
|
7
|
-
# 硬约束:**全程不碰用户全局 ~/.s,凭证不落盘**。clawd 发布用的是 clawd 自己那份 AK(本机 ~/.clawd/secrets/aliyun.env 经 daemon 渲进
|
|
8
|
-
# MCP server env,云上是 pod-setup 渲的 env),而 ~/.s/access.yaml 里的 default 是用户自己账号的
|
|
9
|
-
# Serverless Devs 凭证——它是用户的东西,不是我们的。做法对齐本 kit 处理 aliyun CLI 的方式(凭证只进本次进程的 env):
|
|
10
|
-
# s v3 的 @serverless-devs/credential 有 getEnvKeyPair——进程 env 里同时有 AccountID / AccessKeyID / AccessKeySecret 三个变量,
|
|
11
|
-
# 就自动合成一个叫 `$system_environment_access` 的 access(实测 s 3.1.10 源码),不需要任何 access.yaml。
|
|
12
|
-
# Serverless Devs 的配置家目录仍指到 deploy-kit 名下,但那里只剩组件缓存和日志,没有凭证。
|
|
13
|
-
# ============================================================
|
|
2
|
+
# Source this file and call ensure_credentials before publish/remove.
|
|
3
|
+
# Credentials stay in process env; never overwrite the user global Serverless Devs credentials.
|
|
14
4
|
|
|
15
|
-
#
|
|
16
|
-
# env 没齐时 s 找不到这个名字就 `Not found access: $system_environment_access` 硬报错,而不是默默拿用户
|
|
17
|
-
# ~/.s/access.yaml 里的 default 凭证把 extension 部署进别人的账号。调 s 时都显式带 `--access "$S_ACCESS_ALIAS"`:
|
|
18
|
-
# 命令行参数覆盖 s.yaml 里的 access 字段,老项目(s.yaml 写着 clawd-fc / default)照样走这条。
|
|
5
|
+
# Use the literal environment access alias so missing env cannot silently fall back to the user profile.
|
|
19
6
|
S_ACCESS_ALIAS='$system_environment_access'
|
|
20
7
|
|
|
21
|
-
#
|
|
22
|
-
# AK 在那个进程的 env 里——本机由 daemon 从凭据表(~/.clawd/secrets/aliyun.env)渲进 .mcp.json 声明的 per-server env,
|
|
23
|
-
# 云上由 pod-setup 从仓库 Secrets 渲进 config.toml 的 [mcp_servers.clawd-app-builder].env。不落盘是有意的:
|
|
24
|
-
# 写成文件的话 agent 一个 `cat` 就看见了。deploy-kit 目录下曾经的 `.secrets/aliyun.env[.local]` 两条读法已删。
|
|
25
|
-
#
|
|
26
|
-
# clawd **不随包 ship 任何一把 AK**(2026-09-18 起,见 specs/2026-09-18-aliyun-ak-leak-rotate-design.md:
|
|
27
|
-
# 上一把随 npm 包发出去 69 秒就被 TruffleHog 收割,主账号被风控锁)。env 里没有 = 使用者还没配,fail-loud。
|
|
8
|
+
# Require caller-injected AK values; never ship credentials or read fallback kit credential files.
|
|
28
9
|
ensure_aliyun_env() {
|
|
29
10
|
if [ -z "${ALIBABA_CLOUD_ACCESS_KEY_ID:-}" ] || [ -z "${ALIBABA_CLOUD_ACCESS_KEY_SECRET:-}" ]; then
|
|
30
11
|
echo "❌ 没有阿里云 AK:本机把 ALIBABA_CLOUD_ACCESS_KEY_ID / ALIBABA_CLOUD_ACCESS_KEY_SECRET 两行写进 ~/.clawd/secrets/aliyun.env(daemon 起 clawd-app-builder 时渲进它的 env);云上由 gateway 注入。" >&2
|
|
@@ -33,17 +14,7 @@ ensure_aliyun_env() {
|
|
|
33
14
|
export ALIBABA_CLOUD_REGION_ID="${REGION:?config.env 缺 REGION}"
|
|
34
15
|
}
|
|
35
16
|
|
|
36
|
-
#
|
|
37
|
-
#
|
|
38
|
-
# 为什么非这样不可(实测 aliyun 3.3.17,`--mode AK`):`ALIBABA_CLOUD_ACCESS_KEY_ID` /
|
|
39
|
-
# `_SECRET` / `_REGION_ID` 这三个环境变量在这个模式下**根本不被消费**,于是
|
|
40
|
-
# - 机器上没有 `~/.aliyun/config.json`(全新用户的常态,ensure_toolchain 只装 CLI、
|
|
41
|
-
# 从不跑 `aliyun configure`)→ `ERROR: region can't be empty`,第一次发布就跑不起来;
|
|
42
|
-
# - 机器上有 profile(比如用户自己配过阿里云)→ **静默走用户自己的 profile**,等于拿
|
|
43
|
-
# 用户的阿里云身份去部署 extension,跟 access.yaml 那个坑是同一类。
|
|
44
|
-
# 补 `--region` 一个 flag 不够,AK 也必须是 flag——两者都试过。
|
|
45
|
-
#
|
|
46
|
-
# 用法跟 aliyun 一样,省掉 `--mode AK` 和三个凭证参数:aliyun_ak sts GetCallerIdentity
|
|
17
|
+
# Pass AK and region explicitly: aliyun AK mode can otherwise fall back to the user profile.
|
|
47
18
|
aliyun_ak() {
|
|
48
19
|
aliyun --mode AK \
|
|
49
20
|
--access-key-id "${ALIBABA_CLOUD_ACCESS_KEY_ID:?aliyun_ak 需要先 ensure_aliyun_env}" \
|
|
@@ -52,41 +23,33 @@ aliyun_ak() {
|
|
|
52
23
|
"$@"
|
|
53
24
|
}
|
|
54
25
|
|
|
55
|
-
#
|
|
56
|
-
# (组件缓存 / 日志)指到 deploy-kit 名下——不写 access.yaml,凭证不落盘。
|
|
57
|
-
# s v3 的 rootHome = `$serverless_devs_config_home/.s`(默认 `$HOME/.s`),`s -v` 打印的 s-home 就是它。
|
|
58
|
-
# 前置:ensure_toolchain(aliyun 可执行)+ ensure_aliyun_env(AK 进 env)。
|
|
59
|
-
# 家目录放 $KIT_DIR 下(= persona 目录的 mcp/clawd-app-builder/deploy-kit/.s-home/):只有缓存、没有秘密;
|
|
60
|
-
# persona 仓库的 gitignore 模板(daemon deploy/templates.ts GITIGNORE_LINES)忽略 .s-home/(缓存不该进仓库);
|
|
61
|
-
# 本机每次 daemon 启动 mcp/ 整份换新,组件缓存跟着清,下次 publish 重装一次。
|
|
26
|
+
# Derive AccountID through STS and isolate Serverless Devs cache/config under KIT_DIR; do not write credentials.
|
|
62
27
|
ensure_s_access() {
|
|
63
28
|
local kit="${KIT_DIR:?ensure_s_access 需要调用方先设 KIT_DIR}"
|
|
64
29
|
local s_home="$kit/.s-home"
|
|
65
30
|
local ident acc
|
|
66
31
|
|
|
67
32
|
ident="$(aliyun_ak sts GetCallerIdentity 2>&1)" || {
|
|
68
|
-
#
|
|
69
|
-
# 把 CLI 原文摆出来,让读的人按原文判断。
|
|
33
|
+
# Preserve CLI diagnostics: identity failures are not necessarily bad credentials.
|
|
70
34
|
echo "❌ aliyun sts GetCallerIdentity 失败,拿不到 AccountId。aliyun 原文:" >&2
|
|
71
35
|
echo "$ident" >&2
|
|
72
36
|
echo " 排查顺序:① 上面这行 CLI 原文说的是什么 ② 进程 env 里的 AK 是否有效(本机 ~/.clawd/secrets/aliyun.env、云上仓库 Secrets)——2026-09-18 之前 OTA 发的那把已泄露、不可再用,得换自己的 ③ config.env 的 REGION" >&2
|
|
73
37
|
return 1
|
|
74
38
|
}
|
|
75
|
-
#
|
|
76
|
-
# —— 清理路径尤其不该多一个装不上就跑不了的依赖。
|
|
39
|
+
# Node is guaranteed by clawd; do not add a Python runtime prerequisite.
|
|
77
40
|
acc="$(printf '%s' "$ident" | node -e 'console.log(JSON.parse(require("fs").readFileSync(0,"utf8")).AccountId)')" || {
|
|
78
41
|
echo "❌ 解析 AccountId 失败,aliyun 输出非 JSON:" >&2
|
|
79
42
|
echo "$ident" >&2
|
|
80
43
|
return 1
|
|
81
44
|
}
|
|
82
45
|
|
|
83
|
-
#
|
|
46
|
+
# Serverless Devs credential environment keys are case-sensitive.
|
|
84
47
|
export AccountID="$acc"
|
|
85
48
|
export AccessKeyID="$ALIBABA_CLOUD_ACCESS_KEY_ID"
|
|
86
49
|
export AccessKeySecret="$ALIBABA_CLOUD_ACCESS_KEY_SECRET"
|
|
87
50
|
export serverless_devs_config_home="$s_home"
|
|
88
51
|
mkdir -p "$s_home/.s"
|
|
89
|
-
#
|
|
52
|
+
# Remove legacy persisted credentials from the kit cache.
|
|
90
53
|
rm -f "$s_home/.s/access.yaml"
|
|
91
54
|
}
|
|
92
55
|
|
|
@@ -1,19 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
#
|
|
3
|
-
# 确保发布/清理用到的工具链就绪:Serverless Devs (s) + 阿里云 CLI (aliyun)。
|
|
4
|
-
# 检测环境、缺啥装啥、已装则跳过(幂等)。被 publish.sh / remove-extension.sh
|
|
5
|
-
# 在调用 aliyun/s 之前 `source` 进来,再调 ensure_toolchain。
|
|
6
|
-
#
|
|
7
|
-
# 为什么需要它:FC 发布全靠 aliyun CLI + s 两个外部命令,二者都不随 clawd 安装。
|
|
8
|
-
# 新机器上它们不在 PATH → ensure-credentials.sh 的 `aliyun_ak sts ...` stdout 为空 →
|
|
9
|
-
# JSON.parse 空串报错,且死在第一个 ::stage:: marker 之前 → daemon 只能报 [unknown] 阶段。
|
|
10
|
-
# 这个脚本补的就是这一环。
|
|
11
|
-
#
|
|
12
|
-
# 注意它**只装不配**:不会跑 `aliyun configure`,所以机器上不会因此有 `~/.aliyun/config.json`
|
|
13
|
-
# ——凭证由 ensure-credentials.sh 的 aliyun_ak 每次显式传参,不依赖任何本机 profile。
|
|
14
|
-
# ============================================================
|
|
2
|
+
# Source and call ensure_toolchain before credential setup; install missing CLI tools without configuring user profiles.
|
|
15
3
|
|
|
16
|
-
#
|
|
4
|
+
# Pinned fallback applies only to direct binary download, not Homebrew.
|
|
17
5
|
CLAWD_ALIYUN_CLI_VERSION="${CLAWD_ALIYUN_CLI_VERSION:-3.3.21}"
|
|
18
6
|
|
|
19
7
|
ensure_s() {
|
|
@@ -26,12 +14,10 @@ ensure_s() {
|
|
|
26
14
|
ensure_aliyun() {
|
|
27
15
|
command -v aliyun >/dev/null 2>&1 && return 0
|
|
28
16
|
echo "==> 安装阿里云 CLI (aliyun)…"
|
|
29
|
-
# 优先 brew(有 brew 的 mac 最省事,装到已在 PATH 的 /opt/homebrew/bin | /usr/local/bin)
|
|
30
17
|
if command -v brew >/dev/null 2>&1; then
|
|
31
18
|
brew install aliyun-cli
|
|
32
19
|
return 0
|
|
33
20
|
fi
|
|
34
|
-
# 兜底:官方 release 二进制(没装 brew 的新机器 / Linux server)。
|
|
35
21
|
local os arch bindir="$HOME/.clawd/bin"
|
|
36
22
|
case "$(uname -s)" in
|
|
37
23
|
Darwin) os=macosx ;;
|
|
@@ -57,7 +43,7 @@ ensure_toolchain() {
|
|
|
57
43
|
ensure_aliyun
|
|
58
44
|
}
|
|
59
45
|
|
|
60
|
-
#
|
|
46
|
+
# Direct execution checks the toolchain; sourcing leaves execution to the caller.
|
|
61
47
|
if [ "${BASH_SOURCE[0]}" = "${0}" ]; then
|
|
62
48
|
set -euo pipefail
|
|
63
49
|
ensure_toolchain
|
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
#
|
|
3
|
-
# 用法: new-extension.sh <name> <模板源目录绝对路径> <目标项目目录绝对路径>
|
|
4
|
-
# - 模板源目录:persona 的 extension-kit/examples/<tmpl>(daemon 按 persona 解析后传入)
|
|
5
|
-
# - 目标目录:daemon createProject 已建好(含 .clawd-project.json)的 projects/<name>/
|
|
2
|
+
# Usage: new-extension.sh <name> <template-directory> <precreated-project-directory>
|
|
6
3
|
set -euo pipefail
|
|
7
4
|
|
|
8
5
|
NAME="${1:?用法: new-extension.sh <name> <模板源目录> <目标目录>}"
|
|
@@ -14,20 +11,15 @@ DEST="${3:?缺目标目录}"
|
|
|
14
11
|
echo "❌ 目标目录无效(必须已存在且含 .clawd-project.json): $DEST" >&2; exit 1
|
|
15
12
|
}
|
|
16
13
|
|
|
17
|
-
#
|
|
14
|
+
# Refuse to overwrite a project containing anything beyond the daemon-created marker.
|
|
18
15
|
remaining="$(find "$DEST" -mindepth 1 -maxdepth 1 ! -name '.clawd-project.json' -print -quit)"
|
|
19
16
|
[ -z "$remaining" ] || { echo "❌ 目标已有内容(非空目录): $DEST" >&2; exit 1; }
|
|
20
17
|
|
|
21
|
-
#
|
|
22
|
-
# (tslib 等传递依赖)。cp 到新项目后这些软链 dangling(仍指模板原始路径);而 pnpm install 见
|
|
23
|
-
# node_modules 已在 + lockfile 没变会直接 skip、不重建软链 → 运行/打包时 Cannot find module。
|
|
24
|
-
# 新项目本来就要 install,故 scaffold 一律不拷 node_modules(保留 lockfile 以锁版本)。
|
|
18
|
+
# Exclude node_modules: copied pnpm symlinks can retain template paths, and install may skip rebuilding them.
|
|
25
19
|
rsync -a --exclude='node_modules' "$SRC/" "$DEST/"
|
|
26
20
|
|
|
27
|
-
#
|
|
28
|
-
#
|
|
29
|
-
# publish.sh 和 remove-extension.sh 都 source ext.conf 共用同一个 slug。
|
|
30
|
-
# Agent 命名 Supabase 表时务必带上: <APP_NAME>_<SLUG>_<table>(见 CLAUDE.md「后端:supabase」)。
|
|
21
|
+
# Persist a random slug to reduce shared FC/DNS/table name collisions; publish and removal reuse it.
|
|
22
|
+
# Table names must include APP_NAME and SLUG to isolate this extension.
|
|
31
23
|
SLUG="$(openssl rand -hex 2)"
|
|
32
24
|
{
|
|
33
25
|
echo ""
|
|
@@ -39,11 +31,7 @@ SLUG="$(openssl rand -hex 2)"
|
|
|
39
31
|
echo "SLUG=${SLUG}"
|
|
40
32
|
} >> "$DEST/ext.conf"
|
|
41
33
|
|
|
42
|
-
#
|
|
43
|
-
# 这里用**本进程 env**(daemon spawn 时从凭据表注进来的,见 daemon/src/deploy/secret-table.ts)
|
|
44
|
-
# 展开成同目录的 .env,新项目 pnpm dev 就能直接跑。
|
|
45
|
-
# 通用实现(persona 无关):扫到哪个 ${VAR} 就取哪个同名变量,没有的渲成空串 + 提示,不 fail
|
|
46
|
-
# ——scaffold 不该因为一把 key 没配就整个失败,缺什么在 .env 里一眼看得出来。
|
|
34
|
+
# Render examples from process env, preserving existing files; missing variables warn and become empty.
|
|
47
35
|
render_env_example() {
|
|
48
36
|
local src="$1" dst="${1%.example}"
|
|
49
37
|
local sed_args=() ph var val
|
|
@@ -56,14 +44,13 @@ render_env_example() {
|
|
|
56
44
|
echo " ⚠️ $(basename "$src") 引用了 \$$var,但本次 scaffold 的环境里没有 → 渲成空串" >&2
|
|
57
45
|
fi
|
|
58
46
|
val="${val//\\/\\\\}"; val="${val//|/\\|}"; val="${val//&/\\&}"
|
|
59
|
-
#
|
|
47
|
+
# BRE braces are literal; [$] avoids treating the placeholder as a regex anchor.
|
|
60
48
|
sed_args+=(-e "s|[\$]{$var}|$val|g")
|
|
61
49
|
done
|
|
62
50
|
if [ ${#sed_args[@]} -eq 0 ]; then cp "$src" "$dst"; else sed "${sed_args[@]}" "$src" > "$dst"; fi
|
|
63
51
|
echo " 📄 渲染 ${dst#$DEST/}(值来自本次进程 env,不写回模板)"
|
|
64
52
|
}
|
|
65
|
-
#
|
|
66
|
-
# 这两个值在渲 .env 之前就地定死,不从进程 env 取——scaffold 的环境里就算有同名变量也不该漏进来。
|
|
53
|
+
# Local development always uses integration names, overriding inherited environment values.
|
|
67
54
|
export APP_ENV=integration
|
|
68
55
|
export TABLE_PREFIX="${NAME}_${SLUG}_int_"
|
|
69
56
|
while IFS= read -r f; do render_env_example "$f"; done < <(find "$DEST" -name '*.env.example' -not -path '*/node_modules/*')
|
|
@@ -1,13 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
#
|
|
3
|
-
# 通用一键发布 extension 到阿里云 FC(persona 无关)
|
|
4
|
-
# 用法: publish.sh <extension目录> <persona根目录> <env>
|
|
5
|
-
# - <persona根目录>: daemon 按 session persona 解析后传入(.../personas/<id>)
|
|
6
|
-
# - <env>: integration | prod(必填,无默认)。integration 发到 <APP_NAME>-<SLUG>-int(测试地址),
|
|
7
|
-
# prod 发到 <APP_NAME>-<SLUG>(老地址不变)。见 spec 2026-09-15-app-builder-integration-env-design
|
|
8
|
-
# - 脚本骨架/bootstrap 来自本脚本所在的 deploy-kit(persona 目录 mcp/clawd-app-builder/ 下);平台凭证在进程 env
|
|
9
|
-
# - persona 注入值/占位符/特化逻辑/persona 密钥来自 <persona根>/extension-kit
|
|
10
|
-
# ============================================================
|
|
2
|
+
# Usage: publish.sh <extension-directory> <persona-root> <integration|prod>
|
|
11
3
|
set -euo pipefail
|
|
12
4
|
|
|
13
5
|
KIT_DIR="$(cd "$(dirname "$0")/.." && pwd)" # deploy-kit 根(persona 目录 mcp/clawd-app-builder/deploy-kit/)
|
|
@@ -16,10 +8,9 @@ EXT_DIR="$(cd "$EXT_DIR" && pwd)"
|
|
|
16
8
|
PERSONA_ROOT="${2:?缺 persona 根目录}"
|
|
17
9
|
PERSONA_KIT="$PERSONA_ROOT/extension-kit"
|
|
18
10
|
|
|
19
|
-
# 1) 配置:persona config.env(含平台值+注入值)+ extension 自身 ext.conf(覆盖)
|
|
20
11
|
[ -f "$PERSONA_KIT/config.env" ] && source "$PERSONA_KIT/config.env"
|
|
21
12
|
[ -f "$EXT_DIR/ext.conf" ] && source "$EXT_DIR/ext.conf"
|
|
22
|
-
# env
|
|
13
|
+
# Set env after sourcing config: only the positional argument chooses the deployment environment.
|
|
23
14
|
APP_ENV="${3:?缺 env(integration|prod)}"
|
|
24
15
|
case "$APP_ENV" in integration|prod) ;; *) echo "❌ env 只能是 integration|prod,收到:$APP_ENV" >&2; exit 1;; esac
|
|
25
16
|
APP_NAME="${APP_NAME:-$(basename "$EXT_DIR")}"
|
|
@@ -27,81 +18,54 @@ CODE_DIR="${CODE_DIR:-./server}"
|
|
|
27
18
|
FC_ENTRY="${FC_ENTRY:-dist/main.js}"
|
|
28
19
|
BUILD_CMD="${BUILD_CMD:-}"
|
|
29
20
|
|
|
30
|
-
#
|
|
31
|
-
# 由 new-extension.sh 锁定 + bake 进 ext.conf。这里的 fallback 兜底两种历史情况:
|
|
32
|
-
# 1) 老项目 (本改动前 scaffold) ext.conf 无 SLUG → 现场生成 + bake,保后续稳定
|
|
33
|
-
# 2) 手动 rm SLUG 或损坏 ext.conf → 同上,fail-safe
|
|
34
|
-
# 派生 (按 env):
|
|
35
|
-
# DEPLOY_NAME=${APP_NAME}-${SLUG}[-int] → FC functionName/子域名/s 项目名 (DNS-safe 连字符)
|
|
36
|
-
# TABLE_PREFIX=${APP_NAME}_${SLUG}_[int_] → Supabase 表 (SQL-safe 下划线,见 remove-extension.sh)
|
|
21
|
+
# Persist a missing slug before deriving stable deployment and table names.
|
|
37
22
|
if [ -z "${SLUG:-}" ]; then
|
|
38
23
|
SLUG="$(openssl rand -hex 2)"
|
|
39
24
|
printf '\nSLUG=%s\n' "$SLUG" >> "$EXT_DIR/ext.conf"
|
|
40
25
|
echo " 📝 锁定 SLUG=$SLUG (含 4 位散列防同名冲突,已 bake 进 ext.conf)"
|
|
41
26
|
fi
|
|
42
27
|
BASE_NAME="${APP_NAME}-${SLUG}"
|
|
43
|
-
#
|
|
44
|
-
# 一眼看域名:带 -int 的就是测试环境。派生段与 publish.spec.ts 的 DERIVE_SNIPPET 同步。
|
|
28
|
+
# Keep resource-name derivation aligned with publish.spec.ts DERIVE_SNIPPET.
|
|
45
29
|
if [ "$APP_ENV" = integration ]; then
|
|
46
30
|
ENV_TAG=int; DEPLOY_NAME="${BASE_NAME}-int"; TABLE_PREFIX="${APP_NAME}_${SLUG}_int_"
|
|
47
31
|
else
|
|
48
32
|
ENV_TAG=prod; DEPLOY_NAME="${BASE_NAME}"; TABLE_PREFIX="${APP_NAME}_${SLUG}_"
|
|
49
33
|
fi
|
|
50
|
-
# 两个环境各自的渲染产物;s 用 name: 键状态,DEPLOY_NAME 不同即隔离。
|
|
51
34
|
S_FILE="s.${ENV_TAG}.yaml"
|
|
52
35
|
echo " 🎯 env=$APP_ENV → $DEPLOY_NAME (表前缀 $TABLE_PREFIX, 渲到 $S_FILE)"
|
|
53
|
-
#
|
|
54
|
-
# 存量项目只有 s.yaml 一行,s.int.yaml 会被当源码推上 GitHub——像 bake SLUG 一样幂等地补一行(改了
|
|
55
|
-
# .gitignore 下次 publish 会被「先推再发」拦一次,推上去的只是 .gitignore,无害)。
|
|
36
|
+
# Generated YAML contains secrets; add the wildcard ignore rule for older projects too.
|
|
56
37
|
if ! grep -qxF 's.*.yaml' "$EXT_DIR/.gitignore" 2>/dev/null; then
|
|
57
38
|
printf 's.*.yaml\n' >> "$EXT_DIR/.gitignore"
|
|
58
39
|
echo " 📝 .gitignore 补 s.*.yaml (渲出的 s.int.yaml / s.prod.yaml 含 key,不进仓库;记得把 .gitignore 推上去)"
|
|
59
40
|
fi
|
|
60
41
|
|
|
61
|
-
# 2) 工具链 + 阿里云凭证(进程 env,本机云上同一条;见 ensure-credentials.sh)
|
|
62
42
|
source "$KIT_DIR/scripts/ensure-toolchain.sh"
|
|
63
43
|
ensure_toolchain
|
|
64
44
|
source "$KIT_DIR/scripts/ensure-credentials.sh"
|
|
65
45
|
ensure_credentials
|
|
66
46
|
|
|
67
|
-
# 3) persona hook(可选):校验/导出 persona 特化运行时变量(如 SESSION_SECRET / APP_BASE_URL / persona 密钥)
|
|
68
47
|
if [ -f "$PERSONA_KIT/hooks/pre-deploy.sh" ]; then
|
|
69
48
|
source "$PERSONA_KIT/hooks/pre-deploy.sh"
|
|
70
49
|
fi
|
|
71
50
|
|
|
72
|
-
# 4) build(框架相关)
|
|
73
51
|
echo "::stage::build"
|
|
74
52
|
if [ -n "$BUILD_CMD" ]; then
|
|
75
53
|
echo "==> build: $BUILD_CMD"
|
|
76
54
|
( cd "$EXT_DIR" && eval "$BUILD_CMD" )
|
|
77
55
|
fi
|
|
78
56
|
|
|
79
|
-
# 5) bootstrap(共享,框架无关)
|
|
80
57
|
cp "$KIT_DIR/contract/bootstrap" "$EXT_DIR/$CODE_DIR/bootstrap"
|
|
81
58
|
chmod +x "$EXT_DIR/$CODE_DIR/bootstrap"
|
|
82
59
|
|
|
83
|
-
# 6) 通用占位符替换渲染 s.yaml:扫 persona s.yaml.tmpl 的所有 __X__,从 env 取同名变量
|
|
84
|
-
#(APP_ENV / TABLE_PREFIX 也走这条:模板里 __APP_ENV__ / __TABLE_PREFIX__ 渲进 FC 的 environmentVariables)
|
|
85
|
-
#
|
|
86
|
-
# 自定义域名段按履约拼接(M1b Step 5c):APP_DOMAIN_SUFFIX 非空 = 部署级履约(域名与通配符
|
|
87
|
-
# 证书都在我们自己的阿里云账号上)→ 把 s.yaml.domain.tmpl 接上;空 = 使用者自带 key,
|
|
88
|
-
# 发布落在他自己的账号里,那里既没有我们的 DNS 也没有我们的证书 → 整段不渲染,用 FC 默认域名。
|
|
89
|
-
# 拼完再走同一套占位符替换,渲染契约本身不分叉。
|
|
90
60
|
TMPL="$PERSONA_KIT/contract/s.yaml.tmpl"
|
|
91
61
|
[ -f "$TMPL" ] || { echo "❌ 缺 $TMPL" >&2; exit 1; }
|
|
92
|
-
#
|
|
93
|
-
# 没有那个文件的 persona(persona-dataclaw-builder)把自定义域名段写死在自己的 s.yaml.tmpl 里,
|
|
94
|
-
# 它压根不参与这套切换,替它选分支只会把它的发布路径改坏。
|
|
62
|
+
# Only personas with a separate domain template participate in domain selection; others keep their own template policy.
|
|
95
63
|
DOMAIN_TMPL="$PERSONA_KIT/contract/s.yaml.domain.tmpl"
|
|
96
64
|
TMPL_RENDER="$TMPL"
|
|
97
65
|
TMPL_COMBINED=""
|
|
98
66
|
if [ -f "$DOMAIN_TMPL" ]; then
|
|
99
67
|
if [ -n "${APP_DOMAIN_SUFFIX:-}" ]; then
|
|
100
|
-
#
|
|
101
|
-
# **必须写全 XXXXXX 模板**:BSD mktemp(macOS)的 `-t prefix` 会自己补随机后缀,
|
|
102
|
-
# GNU coreutils(Linux,= M1b 的 Pod)要求模板以 ≥3 个 X 结尾,`mktemp -t s-yaml-tmpl`
|
|
103
|
-
# 直接 `too few X's in template` 退 1(coreutils 9.1 实测),`set -e` 下当场打死这条
|
|
104
|
-
# 默认路径。deploy-kit 的脚本两套 userland 都要跑,别用 BSD/GNU 语义有分歧的写法。
|
|
68
|
+
# Keep temporary templates outside the project and use XXXXXX for both BSD and GNU mktemp.
|
|
105
69
|
TMPL_COMBINED="$(mktemp "${TMPDIR:-/tmp}/s-yaml-tmpl.XXXXXX")"
|
|
106
70
|
trap 'rm -f "$TMPL_COMBINED"' EXIT
|
|
107
71
|
cat "$TMPL" "$DOMAIN_TMPL" > "$TMPL_COMBINED"
|
|
@@ -114,19 +78,16 @@ fi
|
|
|
114
78
|
sed_args=()
|
|
115
79
|
for ph in $(grep -oE '__[A-Z0-9_]+__' "$TMPL_RENDER" | sort -u); do
|
|
116
80
|
var="${ph#__}"; var="${var%__}"
|
|
117
|
-
# declare -p
|
|
81
|
+
# declare -p distinguishes missing from empty variables on Bash 3.2.
|
|
118
82
|
if ! declare -p "$var" >/dev/null 2>&1; then
|
|
119
83
|
echo "❌ s.yaml.tmpl 占位符 $ph 无对应 env 变量 \$$var" >&2
|
|
120
|
-
#
|
|
121
|
-
# 它起的那个子进程,别的地方起的 shell 拿不到。把这条写进报错里,是因为读它的下一个
|
|
122
|
-
# 读者往往是 agent——不给方向它就会去翻凭据、手动 export 或写回 config.env,
|
|
123
|
-
# 正好把「值不落 persona 目录」这条纪律拆掉。
|
|
84
|
+
# Missing injected credentials must be fixed through the daemon credential table, never written into templates.
|
|
124
85
|
echo " 如果你是手动跑这个脚本:凭据只注给 daemon 起的那个进程,手动跑必然缺。" >&2
|
|
125
86
|
echo " 正确做法是改调 clawd-app-builder 的 publish tool 重跑;**不要**去找 key 手动 export,也不要写回 config.env。" >&2
|
|
126
87
|
echo " 确实是漏配(新增了占位符 / 换了实例):改 daemon 的凭据表(~/.clawd/secrets/),不是改模板。" >&2
|
|
127
88
|
exit 1
|
|
128
89
|
fi
|
|
129
|
-
#
|
|
90
|
+
# Escape backslashes before the other sed replacement characters.
|
|
130
91
|
val="${!var}"
|
|
131
92
|
val="${val//\\/\\\\}"
|
|
132
93
|
val="${val//|/\\|}"
|
|
@@ -135,24 +96,13 @@ for ph in $(grep -oE '__[A-Z0-9_]+__' "$TMPL_RENDER" | sort -u); do
|
|
|
135
96
|
done
|
|
136
97
|
sed "${sed_args[@]}" "$TMPL_RENDER" > "$EXT_DIR/$S_FILE"
|
|
137
98
|
|
|
138
|
-
# 7) deploy + verify
|
|
139
99
|
echo "::stage::deploy"
|
|
140
100
|
echo "==> s deploy ($DEPLOY_NAME, env=$APP_ENV)"
|
|
141
101
|
( cd "$EXT_DIR" && s deploy -y -t "$S_FILE" --access "$S_ACCESS_ALIAS" )
|
|
142
102
|
|
|
143
103
|
echo "::stage::verify"
|
|
144
|
-
#
|
|
145
|
-
#
|
|
146
|
-
# 有 s.yaml.domain.tmpl(app-builder):
|
|
147
|
-
# APP_DOMAIN_SUFFIX 非空 → 自定义域名,从 custom-domains 精确匹配
|
|
148
|
-
# APP_DOMAIN_SUFFIX 为空 → 使用者自带 key,FC 默认域名,从 httpTrigger 取
|
|
149
|
-
# 没有(persona-dataclaw-builder 等):自定义域名段写死在它自己的 s.yaml.tmpl 里,
|
|
150
|
-
# **原样走老路**——它的 functionName 规则、域名策略都跟这套无关,别替它选分支。
|
|
151
|
-
#
|
|
152
|
-
# JSON 解析用 node 不用 python3:目标机器保证有 node(clawd 本身就是 node 跑的),不保证有
|
|
153
|
-
# python。而且原先 `2>/dev/null | python3 -c` 把 CLI 的 stderr 吞了再让 python 抛
|
|
154
|
-
# JSONDecodeError,`set -e` 下脚本直接崩在 traceback 上,真凶(凭证不对 / 超时)全丢——
|
|
155
|
-
# 下面的 fc_get 保留 CLI 原文并 fail-loud。
|
|
104
|
+
# URL discovery must use the same domain-template predicate as rendering.
|
|
105
|
+
# Preserve CLI stderr before parsing JSON with the guaranteed Node runtime.
|
|
156
106
|
fc_get() { # fc_get <path>:调 FC OpenAPI;失败把 CLI 原文写 stderr 后 return 1
|
|
157
107
|
local out rc
|
|
158
108
|
out="$(aliyun_ak fc GET "$1" 2>&1)" && rc=0 || rc=$?
|
|
@@ -174,16 +124,13 @@ console.log((d.customDomains || []).some((c) => c.domainName === target) ? targe
|
|
|
174
124
|
PROD_URL=""
|
|
175
125
|
ALLOW_CD=""
|
|
176
126
|
if [ -f "$DOMAIN_TMPL" ] && [ -z "${APP_DOMAIN_SUFFIX:-}" ]; then
|
|
177
|
-
#
|
|
178
|
-
# 为 httpTrigger(写死是确定的,不是猜的)。
|
|
127
|
+
# The template contract fixes the trigger name to httpTrigger.
|
|
179
128
|
PROD_URL="$(fc_get "/2023-03-30/functions/${DEPLOY_NAME}/triggers/httpTrigger" | node -e '
|
|
180
129
|
const d = JSON.parse(require("fs").readFileSync(0, "utf8"));
|
|
181
130
|
console.log((d.httpTrigger && d.httpTrigger.urlInternet) || "");
|
|
182
131
|
')"
|
|
183
132
|
MISS_HINT="未取到 FC 默认域名(函数 ${DEPLOY_NAME} 的 httpTrigger urlInternet 为空),检查 s deploy 是否真的建出了 httpTrigger"
|
|
184
|
-
# FC
|
|
185
|
-
# 自定义域名就消不掉。使用者自带 key 履约的验收口径本来就是「公网可访问即可」
|
|
186
|
-
# (design ③:per-user 部署用 FC 默认域名,不要求品牌域名),所以降级成警告不判失败。
|
|
133
|
+
# FC default-domain Content-Disposition is tolerated only on the no-custom-domain path.
|
|
187
134
|
ALLOW_CD="--allow-content-disposition"
|
|
188
135
|
else
|
|
189
136
|
EXPECTED_DOM="${DEPLOY_NAME}.${APP_DOMAIN_SUFFIX:-app.clawos.chat}"
|