@open-dy/vibecoding-cli 0.0.1-beta.1
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/README.md +47 -0
- package/assets/schemas/custom-card-schema.json +102 -0
- package/bin/vibecoding +89 -0
- package/bin/vibecoding-dev +308 -0
- package/bin/vibecoding-interest-card.mjs +35 -0
- package/bin/vibecoding.mjs +9 -0
- package/package.json +58 -0
- package/src/adapters/interest-card/opencard/commands.mjs +62 -0
- package/src/adapters/interest-card/opencard/dev-watch.mjs +203 -0
- package/src/adapters/interest-card/opencard/dev_run.sh +126 -0
- package/src/adapters/interest-card/opencard/eslint-plugin-opencard-taro.mjs +278 -0
- package/src/adapters/interest-card/opencard/lint-lynx-css.mjs +676 -0
- package/src/adapters/interest-card/opencard/lint.sh +131 -0
- package/src/adapters/interest-card/opencard/lynx-css-rules.mjs +508 -0
- package/src/adapters/interest-card/opencard/pack.sh +27 -0
- package/src/adapters/interest-card/opencard/validate-opencard-structure.mjs +65 -0
- package/src/adapters/interest-card/opencard/validate-widget-shell.mjs +902 -0
- package/src/adapters/interest-card/opencard/validate.mjs +11 -0
- package/src/adapters/interest-card/opencard/write-web-config.mjs +17 -0
- package/src/build/opencard-environment.mjs +49 -0
- package/src/build/turbo-config.mjs +140 -0
- package/src/cli.mjs +104 -0
- package/src/commands/build.mjs +138 -0
- package/src/commands/db.mjs +14 -0
- package/src/commands/dev.mjs +319 -0
- package/src/commands/lint.mjs +176 -0
- package/src/commands/pack.mjs +169 -0
- package/src/commands/turbo.mjs +36 -0
- package/src/commands/validate.mjs +26 -0
- package/src/commands/verify.mjs +602 -0
- package/src/core/args.mjs +101 -0
- package/src/core/context.mjs +101 -0
- package/src/core/environment.mjs +55 -0
- package/src/core/errors.mjs +14 -0
- package/src/core/ports.mjs +76 -0
- package/src/core/process-output.mjs +25 -0
- package/src/core/runner.mjs +90 -0
- package/src/core/service-log-context.mjs +27 -0
- package/src/core/web-config.mjs +13 -0
- package/src/lint/lint-lynx-css.mjs +244 -0
- package/src/lint/lynx-css-rules.mjs +138 -0
- package/src/preview/dev-watch.mjs +38 -0
- package/src/preview/start-preview.mjs +213 -0
- package/src/preview/watch-card-routing.mjs +89 -0
- package/src/scripts/pack.mjs +15 -0
- package/src/validators/dependencies.mjs +34 -0
- package/src/validators/empty-project.mjs +29 -0
- package/src/validators/feed-card.cjs +156 -0
- package/src/validators/feed-card.mjs +32 -0
- package/src/validators/opencard-structure.mjs +58 -0
- package/src/validators/routes.cjs +470 -0
- package/src/validators/routes.mjs +17 -0
- package/src/validators/sql.mjs +57 -0
- package/src/validators/widget-shell.mjs +107 -0
package/README.md
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# @open-dy/vibecoding-cli
|
|
2
|
+
|
|
3
|
+
抖音 Vibecoding Taro 与 OpenCard 项目的命令行工具。它集中提供开发预览、构建、校验、运行时检查和部署打包能力。
|
|
4
|
+
|
|
5
|
+
除 `turbo` 外的工程命令都必须显式指定 target;CLI 不读取 `.tpl/MANIFEST.json` 做环境判断:
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
vibecoding dev --target taro
|
|
9
|
+
vibecoding build --target opencard
|
|
10
|
+
vibecoding lint --target taro
|
|
11
|
+
vibecoding verify --target taro --auto-start --full
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
当前 target:
|
|
15
|
+
|
|
16
|
+
- `taro`:Taro 主应用、Server,以及项目中存在时的 OpenCard。
|
|
17
|
+
- `opencard`:独立 OpenCard 项目。
|
|
18
|
+
|
|
19
|
+
主要命令:`dev`、`build`、`lint`、`verify`、`pack`、`turbo`、`init-mysql-db`,以及 `validate feed-card|routes|empty|opencard-structure`。
|
|
20
|
+
|
|
21
|
+
Turbo 构建应通过 CLI 包装命令执行,例如 `vibecoding turbo --target opencard run build:raw`。CLI 会读取项目的 `turbo.json`,合并版本化默认配置,并将结果写入项目内的 `.vibecoding/cache/turbo.json`;封闭域状态和 `config/card-routing.json` 也会参与构建环境与缓存输入。`--root-turbo-json` 由 CLI 管理,不能由业务命令覆盖;需要传递其他 Turbo 参数时,放在 `--` 后面。
|
|
22
|
+
|
|
23
|
+
`vibecoding validate feed-card --target taro` 与 `verify` 的 Feed 卡片检查共享同一实现。它保留原模板脚本的校验:OpenCard 与 Feed 文件的存在关系、JSON 语法、`TemplateType=5` / `TemplateStyleType=6`、`APP_ID` / `OPEN_CARD_ID`、全部 Schema 错误,以及通过后尽力更新 `__timestamp`。失败时不改写 Feed 文件。
|
|
24
|
+
|
|
25
|
+
Schema 统一使用 `custom-card-schema.json`。`config/card-routing.json` 的 `cardData.serviceDomain`(`open` / `closed`)是卡片服务域的唯一来源;构建时会据此注入 OpenCard 常量。优先保留项目 `scripts/schemas/`、`feed/` 中的本地 Schema 约束,没有时使用 CLI 内置 Schema。内外场构建后的项目 Schema 仍受支持。
|
|
26
|
+
|
|
27
|
+
两套模板的 `node scripts/check-routes.js` 与 `node scripts/validate-feed-card.js` 均保留为兼容入口,规则统一来自 CLI 的 CJS validator 导出;CLI 命令本身使用对应的 ESM 外层。程序化调用 `validateFeedCard(context, { touchTimestamp: false })` 可禁用时间戳更新。Ajv 由 CLI 自带;OpenCard Lynx CSS 检查复用 `@open-dy/vibecoding-sdk-opencard/eslint/lint-lynx-css`。
|
|
28
|
+
|
|
29
|
+
`pack --target taro` 默认先清理 `/tmp/microapp_artifacts`,由 CLI 编排 TT 生产构建、server 生产构建以及存在的封闭域服务和 OpenCard 构建,再生成归档。包含 `microapp.zip`、`server.zip`、`Dockerfile`、`run.sh`;存在封闭域服务时追加 `closed-domain-server.zip`、`closed-domain-Dockerfile`、`closed-domain-run.sh`,存在路由配置时复制 `card-routing.json`,存在 OpenCard 时生成 `opencard.zip`。两个 server 每次均构建,不受卡片域开关影响。旧项目若自行提供 `build:pack`,CLI 仍会传递 `ARTIFACTS_DIR` 兼容其产物;两套当前模板均不依赖该分支。
|
|
30
|
+
|
|
31
|
+
服务包仅包含 `dist`、生成的 `Dockerfile`、`package.json`、`run.sh` 以及可选的 `sql`、`scripts`。生成 Dockerfile 时按原脚本规则为每条 `COPY package.json` 追加存在的目录,项目源文件不改写。构建/zip 失败保留退出码,临时目录在失败时也会清理。
|
|
32
|
+
|
|
33
|
+
CLI 支持用 `ARTIFACTS_DIR` 指定专用产物目录;为避免误删,拒绝项目、用户主目录、临时目录根及其上级目录。模板的 `pack.sh` 不加载 `.env.dev` 或 `.bashrc`,未传入该变量时默认写入 `/tmp/microapp_artifacts`。
|
|
34
|
+
|
|
35
|
+
Taro 构建统一由 CLI 编排,兴趣卡只在业务域规则上扩展:
|
|
36
|
+
|
|
37
|
+
- 两套 Taro 模板均使用 `vibecoding build --target taro` 和 `vibecoding pack --target taro`。模板不再暴露 `build-all.sh`、`build-pack.sh` 或根项目 `build:pack`。
|
|
38
|
+
- OpenCard 保留独立归档入口:通用小程序使用 `vibecoding pack --target opencard`,兴趣卡使用 `vibecoding-interest-card pack` 注入其 widget / Lynx 域适配;两者都只重建 `opencard.zip`,不清理父项目产物。
|
|
39
|
+
- 兴趣卡自定义导航与 `lucide-taro` 约束位于 SDK 的 `interest-card/eslint/taro` adapter;公共校验规则仍只维护一份。
|
|
40
|
+
- `validate routes --target taro` 保留开放域/封闭域选择、卡片路由单服务归属、前端封闭域开关一致性、Feed 必需接口以及主应用/OpenCard 来源诊断;普通未匹配路由仍只报告,不改为失败。`@open-dy/vibecoding-cli/validators/routes` 支持 CJS/ESM 程序化调用。
|
|
41
|
+
- `@open-dy/vibecoding-cli/preview/dev-watch` 的 `runDevWatch({ root })` 只启动 Taro 预览链路,保留项目 `dev:tt` 脚本、清理旧产物、5 分钟就绪超时、云端/OpenCard 预览地址以及信号退出清理;不会安装依赖或启动数据库。完整开发服务仍使用 `vibecoding dev`。
|
|
42
|
+
|
|
43
|
+
CLI 对外命令和校验入口的 JSDoc 使用 `@desc`、`@params`、`@return`、`@example` 描述行为;仓库级校验器 `pnpm --dir build comments:check:cli` 会在推送前和 CI 中校验这些入口。校验器同时兼容标准 JSDoc 的 `@description`、`@param`、`@returns` 写法,避免编辑器和 TypeDoc 等工具无法解析。
|
|
44
|
+
|
|
45
|
+
## 准出测试
|
|
46
|
+
|
|
47
|
+
在仓库根目录运行 `node packages/scripts/run-release-contracts.mjs`,或在本包目录运行 `pnpm test:release`。该命令构建并验证 SDK、CLI 和隔离消费者;终端进度、JUnit 报告与 push CI 配置见 [packages/TESTING.md](../TESTING.md)。
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$comment": "style_url=https://lf3-static.bytednsdoc.com/obj/eden-cn/kuLauvyM-tyvmahsWulwV-upfbvK/ljhwZthlaukjlkulzlp/ai/feed-card/template/custom-card.png; order=1",
|
|
4
|
+
"title": "自定义卡(TemplateStyleType = 6)",
|
|
5
|
+
"description": "适用场景:\n- 卡片内容区域的样式可以完全自定义\n- 业务方需要完全自定义卡片视觉与跳转,不复用任何标准模板",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"properties": {
|
|
8
|
+
"AppID": {
|
|
9
|
+
"type": "string",
|
|
10
|
+
"description": "执行 `printf '%s' \"$APP_ID\"` 获取环境变量 APP_ID 的值,将命令的标准输出原样填入本字段;若输出为空,则填空字符串"
|
|
11
|
+
},
|
|
12
|
+
"CardAppID": {
|
|
13
|
+
"type": "string",
|
|
14
|
+
"description": "卡片 AppID,执行 `printf '%s' \"$OPEN_CARD_ID\"` 获取环境变量 OPEN_CARD_ID 的值,将命令的标准输出原样填入本字段;若输出为空,则填空字符串"
|
|
15
|
+
},
|
|
16
|
+
"PlanName": {
|
|
17
|
+
"type": "string",
|
|
18
|
+
"description": "使用“场景/业务名 - 核心内容投放计划”格式"
|
|
19
|
+
},
|
|
20
|
+
"TemplateType": {
|
|
21
|
+
"const": 5,
|
|
22
|
+
"description": "⚠️ 必须固定输出 5(动态卡)"
|
|
23
|
+
},
|
|
24
|
+
"TemplateStyleType": {
|
|
25
|
+
"const": 6,
|
|
26
|
+
"description": "⚠️ 必须固定输出 6(自定义卡)"
|
|
27
|
+
},
|
|
28
|
+
"DataFetchMode": {
|
|
29
|
+
"const": "dynamic",
|
|
30
|
+
"description": "⚠️ 必须固定输出 \"dynamic\""
|
|
31
|
+
},
|
|
32
|
+
"CardBackgroundUrl": {
|
|
33
|
+
"type": "string",
|
|
34
|
+
"description": "必须调用图片生成工具获取真实的图片 URL,自动生成符合预期的背景图片。此图片将作为 Feed 流场景的全屏背景底图,画面内容必须延伸至画面边缘,完全铺满整个画布。严禁出现手机设备外框、样机模型、任何形式的边框,严禁将内容仅集中在中间而导致四周留白或黑边。图片宽高比严格为`9:16`竖图。必须完整保留原始 URL 及全部 query 参数,禁止截断、改写或丢弃任何参数(如 x-expires)。",
|
|
35
|
+
"pattern": "^https://"
|
|
36
|
+
},
|
|
37
|
+
"Button": {
|
|
38
|
+
"type": "string",
|
|
39
|
+
"minLength": 4,
|
|
40
|
+
"maxLength": 6,
|
|
41
|
+
"description": "行动按钮文案,保持 4 到 6 个字,使用符合场景的引导性动作词"
|
|
42
|
+
},
|
|
43
|
+
"RecommendReasonPrefix": {
|
|
44
|
+
"type": "string",
|
|
45
|
+
"minLength": 2,
|
|
46
|
+
"maxLength": 4,
|
|
47
|
+
"pattern": "^[a-zA-Z0-9\\u4e00-\\u9fa5]+$",
|
|
48
|
+
"description": "结合内容特点生成推荐理由前缀,与 Suffix 拼接后共同表达完整推荐理由,避免重复;Prefix 与 Suffix 拼接后的总长度必须在 2~10 个字之间(即两者长度之和 ≥ 2 且 ≤ 10),仅支持中英文、数字,不可输入特殊符号"
|
|
49
|
+
},
|
|
50
|
+
"RecommendReasonSuffix": {
|
|
51
|
+
"type": "string",
|
|
52
|
+
"minLength": 2,
|
|
53
|
+
"maxLength": 6,
|
|
54
|
+
"pattern": "^[a-zA-Z0-9\\u4e00-\\u9fa5]+$",
|
|
55
|
+
"description": "结合内容特点生成推荐理由后缀,强调内容的吸引力和浏览价值;Prefix 与 Suffix 拼接后的总长度必须在 2~10 个字之间(即两者长度之和 ≥ 2 且 ≤ 10),仅支持中英文、数字,不可输入特殊符号"
|
|
56
|
+
},
|
|
57
|
+
"PrimaryColor": {
|
|
58
|
+
"type": "string",
|
|
59
|
+
"description": "主色调。取脚本输出的 baseColor.hex 写入。仅当用户明确指定主色调时才自行覆盖。颜色格式为十六进制,例如:#051328"
|
|
60
|
+
},
|
|
61
|
+
"ContentInfoList": {
|
|
62
|
+
"type": "array",
|
|
63
|
+
"minItems": 1,
|
|
64
|
+
"maxItems": 1,
|
|
65
|
+
"description": "固定 1 项,仅承载跳转信息",
|
|
66
|
+
"items": {
|
|
67
|
+
"type": "object",
|
|
68
|
+
"properties": {
|
|
69
|
+
"PageIndex": {
|
|
70
|
+
"type": "string",
|
|
71
|
+
"description": "必填,小程序页面路径参数。按以下逻辑判断:\n\nStep 1. 检查AppID值\n ├─ 如果是 tt07e3715e98c9aac0(默认小程序)→ 执行Step 2\n └─ 如果是其他值(自定义小程序)→ 如果用户输入了 path 则取用户输入值,没有则直接传 \"\" 空即可\n\nStep 2. 默认小程序场景下,按业务约定生成对应的 pages 路径与 query 参数。不能以 http 开头。",
|
|
72
|
+
"pattern": "^(?!http).*$"
|
|
73
|
+
},
|
|
74
|
+
"PageURL": {
|
|
75
|
+
"type": "string",
|
|
76
|
+
"minLength": 1,
|
|
77
|
+
"description": "必填,必须调用“获取小程序分享链接工具”生成,取结果中的 UgUrl。必填参数配置:AppId;Query 为生成的 PageIndex;ShareChannel 和 Channel 都为 \"copy\"。注意:如果 AppId 或者 PageIndex 发生修改,该字段也需要重新生成。"
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
"required": ["PageIndex", "PageURL"],
|
|
81
|
+
"additionalProperties": false
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
"required": [
|
|
86
|
+
"AppID",
|
|
87
|
+
"CardAppID",
|
|
88
|
+
"PlanName",
|
|
89
|
+
"TemplateType",
|
|
90
|
+
"TemplateStyleType",
|
|
91
|
+
"DataFetchMode",
|
|
92
|
+
"CardBackgroundUrl",
|
|
93
|
+
"Button",
|
|
94
|
+
"RecommendReasonPrefix",
|
|
95
|
+
"RecommendReasonSuffix",
|
|
96
|
+
"ContentInfoList"
|
|
97
|
+
],
|
|
98
|
+
"additionalProperties": false,
|
|
99
|
+
"patternProperties": {
|
|
100
|
+
"^__timestamp$": {}
|
|
101
|
+
}
|
|
102
|
+
}
|
package/bin/vibecoding
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -Eeuo pipefail
|
|
3
|
+
|
|
4
|
+
SOURCE_PATH="${BASH_SOURCE[0]}"
|
|
5
|
+
while [[ -h "${SOURCE_PATH}" ]]; do
|
|
6
|
+
SOURCE_DIR="$(CDPATH= cd -P -- "$(dirname -- "${SOURCE_PATH}")" && pwd)"
|
|
7
|
+
SOURCE_PATH="$(readlink "${SOURCE_PATH}")"
|
|
8
|
+
if [[ "${SOURCE_PATH}" != /* ]]; then
|
|
9
|
+
SOURCE_PATH="${SOURCE_DIR}/${SOURCE_PATH}"
|
|
10
|
+
fi
|
|
11
|
+
done
|
|
12
|
+
BIN_DIR="$(CDPATH= cd -P -- "$(dirname -- "${SOURCE_PATH}")" && pwd)"
|
|
13
|
+
CALLER_DIR="$(pwd)"
|
|
14
|
+
ORIGINAL_PATH="${PATH:-}"
|
|
15
|
+
NODE_BIN="$(command -v node || true)"
|
|
16
|
+
|
|
17
|
+
resolve_env_root() {
|
|
18
|
+
local requested_cwd="${CALLER_DIR}"
|
|
19
|
+
local expect_cwd=0
|
|
20
|
+
local argument
|
|
21
|
+
|
|
22
|
+
for argument in "$@"; do
|
|
23
|
+
if [[ "${expect_cwd}" -eq 1 ]]; then
|
|
24
|
+
requested_cwd="${argument}"
|
|
25
|
+
expect_cwd=0
|
|
26
|
+
continue
|
|
27
|
+
fi
|
|
28
|
+
case "${argument}" in
|
|
29
|
+
--cwd)
|
|
30
|
+
expect_cwd=1
|
|
31
|
+
;;
|
|
32
|
+
--cwd=*)
|
|
33
|
+
requested_cwd="${argument#--cwd=}"
|
|
34
|
+
;;
|
|
35
|
+
esac
|
|
36
|
+
done
|
|
37
|
+
|
|
38
|
+
if [[ "${expect_cwd}" -eq 1 ]]; then
|
|
39
|
+
printf '%s' "${CALLER_DIR}"
|
|
40
|
+
return
|
|
41
|
+
fi
|
|
42
|
+
if [[ "${requested_cwd}" != /* ]]; then
|
|
43
|
+
requested_cwd="${CALLER_DIR}/${requested_cwd}"
|
|
44
|
+
fi
|
|
45
|
+
if [[ -d "${requested_cwd}" ]]; then
|
|
46
|
+
CDPATH= cd -P -- "${requested_cwd}" && pwd
|
|
47
|
+
return
|
|
48
|
+
fi
|
|
49
|
+
printf '%s' "${requested_cwd}"
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if [[ -n "${HOME:-}" && -f "${HOME}/.bashrc" ]]; then
|
|
53
|
+
set +u
|
|
54
|
+
# shellcheck disable=SC1090
|
|
55
|
+
source "${HOME}/.bashrc" >/dev/null 2>&1 || true
|
|
56
|
+
set -u
|
|
57
|
+
cd "${CALLER_DIR}"
|
|
58
|
+
fi
|
|
59
|
+
|
|
60
|
+
ENV_ROOT="$(resolve_env_root "$@")"
|
|
61
|
+
ENV_FILE="${ENV_ROOT}/.env.dev"
|
|
62
|
+
if [[ -f "${ENV_FILE}" ]]; then
|
|
63
|
+
set -a
|
|
64
|
+
# shellcheck disable=SC1090
|
|
65
|
+
source "${ENV_FILE}"
|
|
66
|
+
set +a
|
|
67
|
+
cd "${CALLER_DIR}"
|
|
68
|
+
fi
|
|
69
|
+
|
|
70
|
+
if [[ -n "${ORIGINAL_PATH}" ]]; then
|
|
71
|
+
PATH="${PATH:-}:${ORIGINAL_PATH}"
|
|
72
|
+
export PATH
|
|
73
|
+
fi
|
|
74
|
+
|
|
75
|
+
NODE_BIN_AFTER_SOURCE="$(command -v node || true)"
|
|
76
|
+
if [[ -n "${NODE_BIN_AFTER_SOURCE}" ]]; then
|
|
77
|
+
NODE_BIN="${NODE_BIN_AFTER_SOURCE}"
|
|
78
|
+
fi
|
|
79
|
+
|
|
80
|
+
if [[ -z "${NODE_BIN}" ]]; then
|
|
81
|
+
echo "vibecoding requires node to be available in PATH" >&2
|
|
82
|
+
exit 127
|
|
83
|
+
fi
|
|
84
|
+
|
|
85
|
+
if [[ "${1:-}" == "dev" ]]; then
|
|
86
|
+
exec bash "${BIN_DIR}/vibecoding-dev" "${NODE_BIN}" "${BIN_DIR}/vibecoding.mjs" "$@"
|
|
87
|
+
fi
|
|
88
|
+
|
|
89
|
+
exec "${NODE_BIN}" "${BIN_DIR}/vibecoding.mjs" "$@"
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -Eeuo pipefail
|
|
3
|
+
|
|
4
|
+
NODE_BIN="$1"
|
|
5
|
+
CLI_ENTRY="$2"
|
|
6
|
+
shift 2
|
|
7
|
+
|
|
8
|
+
resolve_project_root() {
|
|
9
|
+
local requested_cwd="$(pwd)"
|
|
10
|
+
local expect_cwd=0
|
|
11
|
+
local argument
|
|
12
|
+
|
|
13
|
+
for argument in "$@"; do
|
|
14
|
+
if [[ "${expect_cwd}" -eq 1 ]]; then
|
|
15
|
+
requested_cwd="${argument}"
|
|
16
|
+
expect_cwd=0
|
|
17
|
+
continue
|
|
18
|
+
fi
|
|
19
|
+
case "${argument}" in
|
|
20
|
+
--cwd)
|
|
21
|
+
expect_cwd=1
|
|
22
|
+
;;
|
|
23
|
+
--cwd=*)
|
|
24
|
+
requested_cwd="${argument#--cwd=}"
|
|
25
|
+
;;
|
|
26
|
+
esac
|
|
27
|
+
done
|
|
28
|
+
|
|
29
|
+
if [[ "${expect_cwd}" -eq 1 ]]; then
|
|
30
|
+
echo "vibecoding dev requires a value after --cwd" >&2
|
|
31
|
+
return 2
|
|
32
|
+
fi
|
|
33
|
+
CDPATH= cd -P -- "${requested_cwd}" && pwd
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
PROJECT_ROOT="$(resolve_project_root "$@")"
|
|
37
|
+
resolve_target() {
|
|
38
|
+
local target=""
|
|
39
|
+
local expect_target=0
|
|
40
|
+
local argument
|
|
41
|
+
|
|
42
|
+
for argument in "$@"; do
|
|
43
|
+
if [[ "${expect_target}" -eq 1 ]]; then
|
|
44
|
+
target="${argument}"
|
|
45
|
+
expect_target=0
|
|
46
|
+
continue
|
|
47
|
+
fi
|
|
48
|
+
case "${argument}" in
|
|
49
|
+
--target)
|
|
50
|
+
expect_target=1
|
|
51
|
+
;;
|
|
52
|
+
--target=*)
|
|
53
|
+
target="${argument#--target=}"
|
|
54
|
+
;;
|
|
55
|
+
esac
|
|
56
|
+
done
|
|
57
|
+
printf '%s' "${target}"
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
TARGET="$(resolve_target "$@")"
|
|
61
|
+
RUNTIME_PROJECT_ROOT="${PROJECT_ROOT}"
|
|
62
|
+
if [[ "${TARGET}" == "opencard" ]]; then
|
|
63
|
+
RUNTIME_PROJECT_ROOT="$(CDPATH= cd -P -- "${PROJECT_ROOT}/.." && pwd)"
|
|
64
|
+
fi
|
|
65
|
+
|
|
66
|
+
if [[ -n "${VIBECODING_RUNTIME_ROOT:-}" ]]; then
|
|
67
|
+
RUNTIME_ROOT="${VIBECODING_RUNTIME_ROOT}"
|
|
68
|
+
else
|
|
69
|
+
case "$(uname -s)" in
|
|
70
|
+
Linux)
|
|
71
|
+
RUNTIME_ROOT="/workspace/tmp"
|
|
72
|
+
;;
|
|
73
|
+
Darwin)
|
|
74
|
+
RUNTIME_ROOT="${RUNTIME_PROJECT_ROOT}/.tmp/microapp-dev"
|
|
75
|
+
;;
|
|
76
|
+
*)
|
|
77
|
+
echo "vibecoding dev only supports Linux and macOS" >&2
|
|
78
|
+
exit 1
|
|
79
|
+
;;
|
|
80
|
+
esac
|
|
81
|
+
fi
|
|
82
|
+
|
|
83
|
+
SCRIPT_PID_FILE="${RUNTIME_ROOT}/microapp-dev-script.pid"
|
|
84
|
+
DEV_PID_FILE="${RUNTIME_ROOT}/microapp-dev-run.pid"
|
|
85
|
+
DEFAULT_LOG_FILE="${RUNTIME_ROOT}/microapp-logs/dev.log"
|
|
86
|
+
LOG_FILE="${LOG_FILE:-${DEFAULT_LOG_FILE}}"
|
|
87
|
+
LOG_DIR="$(dirname "${LOG_FILE}")"
|
|
88
|
+
LOG_SEPARATOR="--- DEV_RUN_START ---"
|
|
89
|
+
STDOUT_PIPE="${RUNTIME_ROOT}/microapp-dev-stdout.pipe"
|
|
90
|
+
STDERR_PIPE="${RUNTIME_ROOT}/microapp-dev-stderr.pipe"
|
|
91
|
+
DEV_PID=""
|
|
92
|
+
STDOUT_LOGGER_PID=""
|
|
93
|
+
STDERR_LOGGER_PID=""
|
|
94
|
+
|
|
95
|
+
is_valid_pid() {
|
|
96
|
+
[[ "${1:-}" =~ ^[1-9][0-9]*$ ]]
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
read_pid_file() {
|
|
100
|
+
local pid_file="$1"
|
|
101
|
+
local pid=""
|
|
102
|
+
if [[ -f "${pid_file}" ]]; then
|
|
103
|
+
pid="$(tr -d '[:space:]' < "${pid_file}" 2>/dev/null || true)"
|
|
104
|
+
fi
|
|
105
|
+
if is_valid_pid "${pid}"; then
|
|
106
|
+
printf '%s' "${pid}"
|
|
107
|
+
fi
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
kill_process_group_if_owned() {
|
|
111
|
+
local pid="$1"
|
|
112
|
+
# kill(2) only resolves -PID when PID is a process-group leader. For ordinary
|
|
113
|
+
# descendants this is a harmless no-op; for detached services it kills the
|
|
114
|
+
# complete group, including descendants that have already been re-parented.
|
|
115
|
+
kill -KILL -- "-${pid}" 2>/dev/null || true
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
kill_process_tree() {
|
|
119
|
+
local pid="$1"
|
|
120
|
+
local children=""
|
|
121
|
+
local child
|
|
122
|
+
|
|
123
|
+
if ! is_valid_pid "${pid}" || [[ "${pid}" == "$$" || "${pid}" == "${PPID}" ]]; then
|
|
124
|
+
return
|
|
125
|
+
fi
|
|
126
|
+
if ! kill -0 "${pid}" 2>/dev/null; then
|
|
127
|
+
return
|
|
128
|
+
fi
|
|
129
|
+
|
|
130
|
+
# Freeze each parent before walking its children so it cannot create more work
|
|
131
|
+
# while the tree is being torn down.
|
|
132
|
+
kill -STOP "${pid}" 2>/dev/null || true
|
|
133
|
+
children="$(pgrep -P "${pid}" 2>/dev/null || true)"
|
|
134
|
+
for child in ${children}; do
|
|
135
|
+
kill_process_tree "${child}"
|
|
136
|
+
done
|
|
137
|
+
|
|
138
|
+
# The CLI starts long-running services as detached process-group leaders.
|
|
139
|
+
# Killing their group also covers descendants that changed their parent.
|
|
140
|
+
kill_process_group_if_owned "${pid}"
|
|
141
|
+
kill -KILL "${pid}" 2>/dev/null || true
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
cleanup_previous_run() {
|
|
145
|
+
local old_script_pid=""
|
|
146
|
+
local old_dev_pid=""
|
|
147
|
+
|
|
148
|
+
old_script_pid="$(read_pid_file "${SCRIPT_PID_FILE}")"
|
|
149
|
+
old_dev_pid="$(read_pid_file "${DEV_PID_FILE}")"
|
|
150
|
+
|
|
151
|
+
if [[ -n "${old_script_pid}" && "${old_script_pid}" != "$$" ]]; then
|
|
152
|
+
echo "[vibecoding:dev] cleaning previous supervisor process tree (PID ${old_script_pid})"
|
|
153
|
+
kill_process_tree "${old_script_pid}"
|
|
154
|
+
fi
|
|
155
|
+
if [[ -n "${old_dev_pid}" && "${old_dev_pid}" != "${old_script_pid}" && "${old_dev_pid}" != "$$" ]]; then
|
|
156
|
+
echo "[vibecoding:dev] cleaning previous CLI process tree (PID ${old_dev_pid})"
|
|
157
|
+
kill_process_tree "${old_dev_pid}"
|
|
158
|
+
fi
|
|
159
|
+
|
|
160
|
+
rm -f "${SCRIPT_PID_FILE}" "${DEV_PID_FILE}"
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
iso_timestamp() {
|
|
164
|
+
TZ=Asia/Shanghai date "+%Y-%m-%d %H:%M:%S"
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
strip_ansi() {
|
|
168
|
+
printf '%s' "$1" | sed $'s/\x1b\\[[0-9;]*[a-zA-Z]//g'
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
is_compile_error_start() {
|
|
172
|
+
local plain="$1"
|
|
173
|
+
[[ "${plain}" =~ Unexpected[[:space:]]+token ]] && return 0
|
|
174
|
+
[[ "${plain}" =~ SyntaxError|TypeError|ReferenceError|EvalError ]] && return 0
|
|
175
|
+
[[ "${plain}" =~ ELIFECYCLE|Failed[[:space:]]+to[[:space:]]+compile|Build[[:space:]]+failed ]] && return 0
|
|
176
|
+
[[ "${plain}" =~ error[[:space:]]+TS[0-9]+ ]] && return 0
|
|
177
|
+
[[ "${plain}" =~ ERROR[[:space:]]+in[[:space:]] ]] && return 0
|
|
178
|
+
[[ "${plain}" =~ Module[[:space:]]+not[[:space:]]+found|Cannot[[:space:]]+find[[:space:]]+module ]] && return 0
|
|
179
|
+
[[ "${plain}" =~ \[commonjs\].*:[[:space:]].+ ]] && return 0
|
|
180
|
+
[[ "${plain}" =~ \[vite:[^]]+\].+:[[:space:]].+ ]] && return 0
|
|
181
|
+
[[ "${plain}" =~ Internal[[:space:]]+server[[:space:]]+error ]] && return 0
|
|
182
|
+
[[ "${plain}" =~ [Ee]rror:[[:space:]] ]] && return 0
|
|
183
|
+
return 1
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
is_compile_error_continuation() {
|
|
187
|
+
local plain="$1"
|
|
188
|
+
[[ -z "${plain// }" ]] && return 0
|
|
189
|
+
[[ "${plain}" =~ [0-9]+[[:space:]]+\| ]] && return 0
|
|
190
|
+
[[ "${plain}" =~ \>[[:space:]]*[0-9]+[[:space:]]+\| ]] && return 0
|
|
191
|
+
[[ "${plain}" =~ \|[[:space:]]+\^ ]] && return 0
|
|
192
|
+
[[ "${plain}" =~ file:[[:space:]]*/ ]] && return 0
|
|
193
|
+
[[ "${plain}" =~ ^[[:space:]]*\^[[:space:]]*$ ]] && return 0
|
|
194
|
+
return 1
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
strip_service_prefix() {
|
|
198
|
+
local plain="$1"
|
|
199
|
+
if [[ "${plain}" =~ ^\[(taro|server|opencard|closed-domain-server)\][[:space:]]+(.*)$ ]]; then
|
|
200
|
+
printf '%s' "${BASH_REMATCH[2]}"
|
|
201
|
+
return
|
|
202
|
+
fi
|
|
203
|
+
printf '%s' "${plain}"
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
prefix_log_stream() {
|
|
207
|
+
local stream="$1"
|
|
208
|
+
local line=""
|
|
209
|
+
local plain=""
|
|
210
|
+
local body=""
|
|
211
|
+
local label="stdout"
|
|
212
|
+
local in_error_block=0
|
|
213
|
+
|
|
214
|
+
while IFS= read -r line || [[ -n "${line}" ]]; do
|
|
215
|
+
plain="$(strip_ansi "${line}")"
|
|
216
|
+
body="$(strip_service_prefix "${plain}")"
|
|
217
|
+
label="stdout"
|
|
218
|
+
if [[ "${stream}" == "stderr" ]]; then
|
|
219
|
+
label="error"
|
|
220
|
+
in_error_block=1
|
|
221
|
+
elif is_compile_error_start "${body}"; then
|
|
222
|
+
label="error"
|
|
223
|
+
in_error_block=1
|
|
224
|
+
elif [[ "${in_error_block}" -eq 1 ]] && is_compile_error_continuation "${body}"; then
|
|
225
|
+
label="error"
|
|
226
|
+
else
|
|
227
|
+
in_error_block=0
|
|
228
|
+
fi
|
|
229
|
+
printf '[%s] [%s] %s\n' "$(iso_timestamp)" "${label}" "${line}"
|
|
230
|
+
done
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
init_logging() {
|
|
234
|
+
mkdir -p "${LOG_DIR}"
|
|
235
|
+
printf '\n%s\n' "${LOG_SEPARATOR}" | tee -a "${LOG_FILE}"
|
|
236
|
+
rm -f "${STDOUT_PIPE}" "${STDERR_PIPE}"
|
|
237
|
+
mkfifo "${STDOUT_PIPE}" "${STDERR_PIPE}"
|
|
238
|
+
|
|
239
|
+
exec 3>&1 4>&2
|
|
240
|
+
prefix_log_stream stdout < "${STDOUT_PIPE}" | tee -a "${LOG_FILE}" >&3 &
|
|
241
|
+
STDOUT_LOGGER_PID=$!
|
|
242
|
+
prefix_log_stream stderr < "${STDERR_PIPE}" | tee -a "${LOG_FILE}" >&4 &
|
|
243
|
+
STDERR_LOGGER_PID=$!
|
|
244
|
+
exec > "${STDOUT_PIPE}" 2> "${STDERR_PIPE}"
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
close_logging() {
|
|
248
|
+
if [[ -z "${STDOUT_LOGGER_PID}" && -z "${STDERR_LOGGER_PID}" ]]; then
|
|
249
|
+
return
|
|
250
|
+
fi
|
|
251
|
+
|
|
252
|
+
exec 1>&3 2>&4
|
|
253
|
+
exec 3>&- 4>&-
|
|
254
|
+
set +e
|
|
255
|
+
[[ -n "${STDOUT_LOGGER_PID}" ]] && wait "${STDOUT_LOGGER_PID}"
|
|
256
|
+
[[ -n "${STDERR_LOGGER_PID}" ]] && wait "${STDERR_LOGGER_PID}"
|
|
257
|
+
set -e
|
|
258
|
+
rm -f "${STDOUT_PIPE}" "${STDERR_PIPE}"
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
remove_owned_pid_file() {
|
|
262
|
+
local pid_file="$1"
|
|
263
|
+
local expected_pid="$2"
|
|
264
|
+
local recorded_pid=""
|
|
265
|
+
recorded_pid="$(read_pid_file "${pid_file}")"
|
|
266
|
+
if [[ "${recorded_pid}" == "${expected_pid}" ]]; then
|
|
267
|
+
rm -f "${pid_file}"
|
|
268
|
+
fi
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
cleanup_current_run() {
|
|
272
|
+
local status=$?
|
|
273
|
+
trap - EXIT INT TERM HUP
|
|
274
|
+
if [[ -n "${DEV_PID}" ]] && kill -0 "${DEV_PID}" 2>/dev/null; then
|
|
275
|
+
echo "[vibecoding:dev] stopping CLI process tree (PID ${DEV_PID})"
|
|
276
|
+
kill_process_tree "${DEV_PID}"
|
|
277
|
+
fi
|
|
278
|
+
remove_owned_pid_file "${DEV_PID_FILE}" "${DEV_PID}"
|
|
279
|
+
remove_owned_pid_file "${SCRIPT_PID_FILE}" "$$"
|
|
280
|
+
close_logging
|
|
281
|
+
exit "${status}"
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
mkdir -p "${RUNTIME_ROOT}"
|
|
285
|
+
cleanup_previous_run
|
|
286
|
+
printf '%s\n' "$$" > "${SCRIPT_PID_FILE}"
|
|
287
|
+
init_logging
|
|
288
|
+
|
|
289
|
+
if [[ ! "${NODE_OPTIONS:-}" =~ --max[-_]old[-_]space[-_]size ]]; then
|
|
290
|
+
NODE_OPTIONS="${NODE_OPTIONS:+${NODE_OPTIONS} }--max-old-space-size=6144"
|
|
291
|
+
export NODE_OPTIONS
|
|
292
|
+
fi
|
|
293
|
+
|
|
294
|
+
trap cleanup_current_run EXIT
|
|
295
|
+
trap 'exit 130' INT
|
|
296
|
+
trap 'exit 143' TERM
|
|
297
|
+
trap 'exit 129' HUP
|
|
298
|
+
|
|
299
|
+
VIBECODING_DEV_SUPERVISED=1 "${NODE_BIN}" "${CLI_ENTRY}" "$@" &
|
|
300
|
+
DEV_PID=$!
|
|
301
|
+
printf '%s\n' "${DEV_PID}" > "${DEV_PID_FILE}"
|
|
302
|
+
echo "[vibecoding:dev] supervisor PID $$, CLI PID ${DEV_PID}, log file: ${LOG_FILE}"
|
|
303
|
+
|
|
304
|
+
set +e
|
|
305
|
+
wait "${DEV_PID}"
|
|
306
|
+
DEV_STATUS=$?
|
|
307
|
+
set -e
|
|
308
|
+
exit "${DEV_STATUS}"
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { parseArgs } from '../src/core/args.mjs'
|
|
3
|
+
import { resolveProjectContext } from '../src/core/context.mjs'
|
|
4
|
+
import { UsageError } from '../src/core/errors.mjs'
|
|
5
|
+
import { runInterestCard } from '../src/adapters/interest-card/opencard/commands.mjs'
|
|
6
|
+
|
|
7
|
+
async function main() {
|
|
8
|
+
const { positionals, flags } = parseArgs(process.argv.slice(2))
|
|
9
|
+
const [command, validator] = positionals
|
|
10
|
+
if (flags.help || !command) {
|
|
11
|
+
console.log(`Usage: vibecoding-interest-card <dev|dev-watch|dev-tt|build|pack|lint|validate> [options]
|
|
12
|
+
|
|
13
|
+
Options:
|
|
14
|
+
--cwd <path> OpenCard project directory (default: current directory)
|
|
15
|
+
--incremental Lint changed files when possible
|
|
16
|
+
|
|
17
|
+
Validators: opencard-structure, widget-shell, lynx-css`)
|
|
18
|
+
return
|
|
19
|
+
}
|
|
20
|
+
for (const name of Object.keys(flags)) {
|
|
21
|
+
if (name !== 'cwd' && !(name === 'incremental' && command === 'lint')) {
|
|
22
|
+
throw new UsageError(`Unsupported option for ${command}: --${name}`)
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
if (positionals.length > (command === 'validate' ? 2 : 1)) {
|
|
26
|
+
throw new UsageError(`Unexpected arguments for ${command}`)
|
|
27
|
+
}
|
|
28
|
+
const context = resolveProjectContext({ cwd: flags.cwd, target: 'opencard' })
|
|
29
|
+
return runInterestCard(context, command, validator, flags)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
main().catch(error => {
|
|
33
|
+
console.error(`[vibecoding-interest-card] ERROR: ${error.message ?? error}`)
|
|
34
|
+
process.exitCode = typeof error?.exitCode === 'number' ? error.exitCode : 1
|
|
35
|
+
})
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { main } from '../src/cli.mjs'
|
|
4
|
+
|
|
5
|
+
main(process.argv.slice(2)).catch((error) => {
|
|
6
|
+
const message = error instanceof Error ? error.message : String(error)
|
|
7
|
+
console.error(`[vibecoding] ERROR: ${message}`)
|
|
8
|
+
process.exitCode = typeof error?.exitCode === 'number' ? error.exitCode : 1
|
|
9
|
+
})
|
package/package.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@open-dy/vibecoding-cli",
|
|
3
|
+
"version": "0.0.1-beta.1",
|
|
4
|
+
"description": "Command orchestration, validation and packaging for Douyin vibecoding Taro and OpenCard projects.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"vibecoding": "./bin/vibecoding",
|
|
8
|
+
"vibecoding-interest-card": "./bin/vibecoding-interest-card.mjs"
|
|
9
|
+
},
|
|
10
|
+
"exports": {
|
|
11
|
+
"./package.json": "./package.json",
|
|
12
|
+
"./preview/dev-watch": "./src/preview/dev-watch.mjs",
|
|
13
|
+
"./validators/routes": {
|
|
14
|
+
"import": "./src/validators/routes.mjs",
|
|
15
|
+
"require": "./src/validators/routes.cjs"
|
|
16
|
+
},
|
|
17
|
+
"./validators/feed-card": {
|
|
18
|
+
"import": "./src/validators/feed-card.mjs",
|
|
19
|
+
"require": "./src/validators/feed-card.cjs"
|
|
20
|
+
},
|
|
21
|
+
"./adapters/interest-card/opencard/*": "./src/adapters/interest-card/opencard/*"
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"bin",
|
|
25
|
+
"src",
|
|
26
|
+
"assets",
|
|
27
|
+
"README.md"
|
|
28
|
+
],
|
|
29
|
+
"scripts": {
|
|
30
|
+
"ci:check": "pnpm run build && pnpm run test",
|
|
31
|
+
"build": "node ./scripts/verify-package.mjs",
|
|
32
|
+
"test": "node ../scripts/run-package-tests.mjs cli",
|
|
33
|
+
"pack:local": "pnpm pack --pack-destination ./artifacts",
|
|
34
|
+
"pack:ppe": "node ./scripts/pack-ppe.mjs",
|
|
35
|
+
"test:release": "node ../scripts/run-release-contracts.mjs",
|
|
36
|
+
"publish:bnpm:ppe": "node ../scripts/ppe-release/cli.mjs vibecoding-cli",
|
|
37
|
+
"publish:bnpm:ppe:dry-run": "node ../scripts/ppe-release/cli.mjs vibecoding-cli --dry-run"
|
|
38
|
+
},
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"ajv": "^8.18.0"
|
|
41
|
+
},
|
|
42
|
+
"peerDependencies": {
|
|
43
|
+
"@open-dy/vibecoding-sdk-taro": "0.0.1-beta.1",
|
|
44
|
+
"@open-dy/vibecoding-sdk-opencard": "0.0.1-beta.1"
|
|
45
|
+
},
|
|
46
|
+
"peerDependenciesMeta": {
|
|
47
|
+
"@open-dy/vibecoding-sdk-taro": {
|
|
48
|
+
"optional": true
|
|
49
|
+
},
|
|
50
|
+
"@open-dy/vibecoding-sdk-opencard": {
|
|
51
|
+
"optional": true
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
"engines": {
|
|
55
|
+
"node": ">=18"
|
|
56
|
+
},
|
|
57
|
+
"license": "UNLICENSED"
|
|
58
|
+
}
|