@open-dy/vibecoding-cli 0.1.1 → 0.1.2-beta.0

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 CHANGED
@@ -32,13 +32,15 @@ Turbo 构建应通过 CLI 包装命令执行,例如 `vibecoding turbo --target
32
32
 
33
33
  Schema 统一使用 `custom-card-schema.json`。`config/card-routing.json` 的 `cardData.serviceDomain`(`open` / `closed`)是卡片服务域的唯一来源;构建时会据此注入 OpenCard 常量。优先保留项目 `scripts/schemas/`、`feed/` 中的本地 Schema 约束,没有时使用 CLI 内置 Schema。内外场构建后的项目 Schema 仍受支持。
34
34
 
35
+ CLI 内置 Schema 与通用小程序模板支持可选 `BgMusicUrl`;填写时必须为 HTTPS 字符串,校验会保留完整 URL。兴趣卡模板的本地 Schema 继续要求必填,不会被 CLI 兜底规则放宽。
36
+
35
37
  两套模板的 `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`。
36
38
 
37
39
  `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` 兼容其产物;两套当前模板均不依赖该分支。
38
40
 
39
41
  服务包仅包含 `dist`、生成的 `Dockerfile`、`package.json`、`run.sh` 以及可选的 `sql`、`scripts`。生成 Dockerfile 时按原脚本规则为每条 `COPY package.json` 追加存在的目录,项目源文件不改写。构建/zip 失败保留退出码,临时目录在失败时也会清理。
40
42
 
41
- `verify` 的静态阶段在构建通过后,保留主工程 `server.zip` 的文件数预检(阈值 `1000`,超限失败)。标准 CLI 模板使用打包共用的清单;自定义 `scripts/pack.sh` 使用其 `REQUIRED_SERVER_INCLUDES` / `OPTIONAL_SERVER_INCLUDES`。脚本缺失或清单无法解析时,warning 并保守统计整个 `server/`;缺少目录或必需文件时明确报告未覆盖范围。检查范围不扩大到封闭域服务,不生成 ZIP,也不验证压缩包大小。静态检查不等待 dev 的安装成功日志;自动启动预览后仍等待安装与端口就绪。
43
+ `verify` 的静态阶段在构建通过后,校验主工程 `server.zip` 的文件数(阈值 `1000`,超限失败),不生成 ZIP 或计算压缩包大小。标准 CLI 模板与打包共享黑名单:任意层级过滤依赖、缓存、测试目录、环境文件和私钥,保留其余业务资源。自定义 `scripts/pack.sh` 支持 `REQUIRED_SERVER_PATHS` / `SERVER_PACK_EXCLUDES` / `SERVER_PACK_EXCLUDE_GLOBS`,兼容旧的 `REQUIRED_SERVER_INCLUDES` / `OPTIONAL_SERVER_INCLUDES`;脚本缺失或无法解析、服务目录或必需文件缺失均失败。`pack` 对普通和封闭域服务应用同一规则,并重写各自的 `.dockerignore`,使 ZIP 与镜像包含相同资源。静态检查不等待 dev 的安装成功日志;自动启动预览后仍等待安装与端口就绪。
42
44
 
43
45
  浏览器验收优先使用 `agent-browser-real`,不可用时使用 `agent-browser`。浏览器命令只在验收阶段查找;CDP / 浏览器基础设施失败会报告 `infra_browser` 和 `runtime_status=unverified`,继续以非零退出码结束,页面与业务错误仍按实际错误判定。
44
46
 
@@ -58,6 +58,11 @@
58
58
  "type": "string",
59
59
  "description": "主色调。取脚本输出的 baseColor.hex 写入。仅当用户明确指定主色调时才自行覆盖。颜色格式为十六进制,例如:#051328"
60
60
  },
61
+ "BgMusicUrl": {
62
+ "type": "string",
63
+ "description": "背景音乐 HTTPS URL,由宿主播放。必须使用真实地址,完整保留 query 参数;填写时不得使用空字符串、占位符或本地路径。",
64
+ "pattern": "^https://"
65
+ },
61
66
  "ContentInfoList": {
62
67
  "type": "array",
63
68
  "minItems": 1,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-dy/vibecoding-cli",
3
- "version": "0.1.1",
3
+ "version": "0.1.2-beta.0",
4
4
  "description": "Command orchestration, validation and packaging for Douyin vibecoding Taro and OpenCard projects.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -43,8 +43,8 @@
43
43
  "ajv": "^8.18.0"
44
44
  },
45
45
  "peerDependencies": {
46
- "@open-dy/vibecoding-sdk-taro": "0.1.1",
47
- "@open-dy/vibecoding-sdk-opencard": "0.1.1"
46
+ "@open-dy/vibecoding-sdk-taro": "0.1.0-beta.0",
47
+ "@open-dy/vibecoding-sdk-opencard": "0.1.2-beta.0"
48
48
  },
49
49
  "peerDependenciesMeta": {
50
50
  "@open-dy/vibecoding-sdk-taro": {
@@ -60,7 +60,7 @@
60
60
  "license": "UNLICENSED",
61
61
  "publishConfig": {
62
62
  "registry": "https://registry.npmjs.org/",
63
- "tag": "latest",
63
+ "tag": "beta",
64
64
  "access": "public"
65
65
  }
66
66
  }
@@ -2,14 +2,14 @@
2
2
  "schema": 1,
3
3
  "producer": "vibecoding-cli",
4
4
  "packageName": "@open-dy/vibecoding-cli",
5
- "version": "0.1.1",
5
+ "version": "0.1.2-beta.0",
6
6
  "source": {
7
- "digest": "sha256-77d7b0ea1db52eb5a77054124658253f9fd7ad00337333b458128404d0553c84",
7
+ "digest": "sha256-9edd66d93aac634a22044fed8f9604c4ecc19427961ec47e0277d7271c1eeb5e",
8
8
  "files": 85
9
9
  },
10
- "payload": "sha256-2d69be237f533820ad35349d4403ccd8307ffb897211a3cbef4a522d72840247",
10
+ "payload": "sha256-e2191ec6f36d8e7e22d5f257216a6da9268634869ff7fe1562faab1499c446f5",
11
11
  "toolchain": {
12
- "node": "v22.22.3",
12
+ "node": "v22.18.0",
13
13
  "packageManager": null
14
14
  }
15
15
  }
@@ -17,7 +17,7 @@ import { CliError } from '../core/errors.mjs'
17
17
  import { resolveProjectContext } from '../core/context.mjs'
18
18
  import { buildOpenCard, buildPackInputs } from './build.mjs'
19
19
  import { readCardRouting } from '../build/opencard-environment.mjs'
20
- import { REQUIRED_SERVER_INCLUDES, OPTIONAL_SERVER_INCLUDES } from '../validators/server-artifact.mjs'
20
+ import { REQUIRED_SERVER_INCLUDES, isServerPackExcluded, serverDockerignore } from '../validators/server-artifact.mjs'
21
21
 
22
22
  const DEFAULT_ARTIFACTS = '/tmp/microapp_artifacts'
23
23
 
@@ -65,33 +65,23 @@ function resolveArtifactsDirectory(root, requested) {
65
65
  return absolute
66
66
  }
67
67
 
68
- async function zipDirectory(directory, output, { removeExisting = true } = {}) {
68
+ async function zipDirectory(directory, output) {
69
69
  if (!isDirectory(directory)) throw new CliError(`Pack input is missing: ${directory}`)
70
- if (removeExisting) rmSync(output, { force: true })
70
+ rmSync(output, { force: true })
71
71
  await runPackProcess('zip', ['-rq', output, '.'], { cwd: directory })
72
72
  }
73
73
 
74
74
  function createServerStage(server, stage) {
75
- if (!isDirectory(server)) throw new CliError(`Server pack input is missing: ${server}`)
76
75
  for (const name of REQUIRED_SERVER_INCLUDES) {
77
76
  if (!existsSync(path.join(server, name))) {
78
77
  throw new CliError(`Server pack input is missing: ${path.join(server, name)}`)
79
78
  }
80
79
  }
81
- for (const name of [...REQUIRED_SERVER_INCLUDES, ...OPTIONAL_SERVER_INCLUDES].filter(name => name !== 'Dockerfile')) {
82
- const source = path.join(server, name)
83
- if (existsSync(source)) cpSync(source, path.join(stage, name), { recursive: true })
84
- }
85
- let dockerfile = readFileSync(path.join(server, 'Dockerfile'), 'utf8')
86
- const additions = []
87
- if (isDirectory(path.join(server, 'scripts'))) additions.push('COPY ./scripts ./scripts')
88
- if (isDirectory(path.join(server, 'sql'))) additions.push('COPY ./sql ./sql')
89
- // Match awk's per-line insertion, including multiple COPY instructions and
90
- // its final newline. Files named sql/scripts are copied but are not folders.
91
- dockerfile = dockerfile.split('\n').filter((_, index, lines) => index < lines.length - 1 || lines[index] !== '')
92
- .flatMap(line => /^COPY package\.json/.test(line) ? [line, ...additions] : [line])
93
- .map(line => `${line}\n`).join('')
94
- writeFileSync(path.join(stage, 'Dockerfile'), dockerfile)
80
+ writeFileSync(path.join(server, '.dockerignore'), serverDockerignore())
81
+ cpSync(server, stage, {
82
+ recursive: true,
83
+ filter: source => source === server || !isServerPackExcluded(path.basename(source)),
84
+ })
95
85
  return stage
96
86
  }
97
87
 
@@ -101,10 +91,14 @@ export async function packOpenCardArtifact(context, artifacts) {
101
91
  }
102
92
 
103
93
  async function packServer(server, artifacts, closed = false) {
94
+ if (!isDirectory(server)) throw new CliError(`Server pack input is missing: ${server}`)
95
+ const archive = path.join(artifacts, closed ? 'closed-domain-server.zip' : 'server.zip')
96
+ // Match pack.sh: discard a hook's archive before checking required inputs.
97
+ rmSync(archive, { force: true })
104
98
  const stage = mkdtempSync(path.join(os.tmpdir(), 'vibecoding-server-pack-'))
105
99
  try {
106
100
  createServerStage(server, stage)
107
- await zipDirectory(stage, path.join(artifacts, closed ? 'closed-domain-server.zip' : 'server.zip'), { removeExisting: false })
101
+ await zipDirectory(stage, archive)
108
102
  cpSync(path.join(stage, 'Dockerfile'), path.join(artifacts, closed ? 'closed-domain-Dockerfile' : 'Dockerfile'))
109
103
  cpSync(path.join(server, 'run.sh'), path.join(artifacts, closed ? 'closed-domain-run.sh' : 'run.sh'))
110
104
  } finally {
@@ -2,9 +2,33 @@ import { existsSync, lstatSync, readFileSync, readdirSync } from 'node:fs'
2
2
  import path from 'node:path'
3
3
  import { CliError } from '../core/errors.mjs'
4
4
 
5
- // Shared with pack: count only paths copied into each server archive.
5
+ // Shared with pack: select the same files for validation, ZIPs and Docker contexts.
6
6
  export const REQUIRED_SERVER_INCLUDES = ['dist', 'Dockerfile', 'package.json', 'run.sh']
7
- export const OPTIONAL_SERVER_INCLUDES = ['sql', 'scripts']
7
+ export const SERVER_PACK_EXCLUDES = [
8
+ 'node_modules', '.git', '.github', '.vscode', '.idea', '.turbo', '.cache',
9
+ 'coverage', '.nyc_output', 'test', 'tests', '__tests__', '.aws', '.ssh',
10
+ '.dockerignore', '.DS_Store',
11
+ ]
12
+ export const SERVER_PACK_EXCLUDE_GLOBS = [
13
+ '.env', '.env.*', '.npmrc', 'id_rsa*', 'id_ed25519*', '*.pem', '*.key',
14
+ '*.p12', '*.pfx', '*.tsbuildinfo', '*.log',
15
+ ]
16
+ const DEFAULT_POLICY = {
17
+ required: REQUIRED_SERVER_INCLUDES,
18
+ excludes: SERVER_PACK_EXCLUDES,
19
+ excludeGlobs: SERVER_PACK_EXCLUDE_GLOBS,
20
+ }
21
+
22
+ export function isServerPackExcluded(name, policy = DEFAULT_POLICY) {
23
+ return policy.excludes.includes(name) || policy.excludeGlobs.some(glob => {
24
+ const escaped = glob.replace(/[.+^${}()|[\]\\]/g, '\\$&').replace(/\*/g, '.*').replace(/\?/g, '.')
25
+ return new RegExp(`^${escaped}$`).test(name)
26
+ })
27
+ }
28
+
29
+ export function serverDockerignore() {
30
+ return [...SERVER_PACK_EXCLUDES, ...SERVER_PACK_EXCLUDE_GLOBS].join('\n') + '\n'
31
+ }
8
32
  const MAX_FILE_COUNT = 1000
9
33
  const CLI_PACK_WRAPPER = [
10
34
  'set -Eeuo pipefail',
@@ -19,7 +43,7 @@ function readPackIncludes(packScriptPath) {
19
43
  const normalized = script.split(/\r?\n/).map(line => line.trim())
20
44
  .filter(line => line && !line.startsWith('#')).join('\n')
21
45
  if (normalized === CLI_PACK_WRAPPER) {
22
- return { required: REQUIRED_SERVER_INCLUDES, optional: OPTIONAL_SERVER_INCLUDES }
46
+ return DEFAULT_POLICY
23
47
  }
24
48
  const parse = name => {
25
49
  const match = script.match(new RegExp(`^\\s*${name}=\\(([^)]*)\\)`, 'm'))
@@ -27,43 +51,39 @@ function readPackIncludes(packScriptPath) {
27
51
  return (match[1].match(/(?:"[^"]*"|'[^']*'|\S)+/g) || [])
28
52
  .map(item => item.replace(/^['"]|['"]$/g, ''))
29
53
  }
54
+ if (/^\s*SERVER_PACK_EXCLUDES=/m.test(script)) {
55
+ return { required: parse('REQUIRED_SERVER_PATHS'), excludes: parse('SERVER_PACK_EXCLUDES'), excludeGlobs: parse('SERVER_PACK_EXCLUDE_GLOBS') }
56
+ }
30
57
  return { required: parse('REQUIRED_SERVER_INCLUDES'), optional: parse('OPTIONAL_SERVER_INCLUDES') }
31
58
  }
32
59
 
33
- function countFiles(target) {
60
+ function countFiles(target, policy) {
34
61
  if (!lstatSync(target).isDirectory()) return 1
35
- return readdirSync(target).reduce((total, name) => total + countFiles(path.join(target, name)), 0)
62
+ return readdirSync(target).reduce((total, name) => total +
63
+ (policy.excludes && isServerPackExcluded(name, policy) ? 0 : countFiles(path.join(target, name), policy)), 0)
36
64
  }
37
65
 
38
66
  export function validateServerArtifactFileCount(serverRoot, { packScriptPath } = {}) {
39
- if (!existsSync(serverRoot)) {
40
- console.warn(`[verify] static:server-artifact-file-count skipped missing server directory: ${serverRoot}.`)
41
- return { skipped: true }
42
- }
43
- let includes = { required: REQUIRED_SERVER_INCLUDES, optional: OPTIONAL_SERVER_INCLUDES }
44
- let countSource = 'the CLI server archive includes'
67
+ if (!existsSync(serverRoot)) throw new CliError(`Server directory is missing: ${serverRoot}`)
68
+ let policy = DEFAULT_POLICY
45
69
  if (packScriptPath) {
46
70
  try {
47
- includes = readPackIncludes(packScriptPath)
48
- countSource = `${packScriptPath} server.zip includes: ${[...includes.required, ...includes.optional].join(', ')}`
71
+ policy = readPackIncludes(packScriptPath)
49
72
  } catch (error) {
50
- includes = null
51
- countSource = 'the entire server directory'
52
- console.warn(`[verify] static:server-artifact-file-count could not parse ${packScriptPath}; conservatively counting ${serverRoot}: ${error.message}.`)
73
+ throw new CliError(`Could not read server pack policy from ${packScriptPath}: ${error.message}`)
53
74
  }
54
75
  }
55
- const missing = includes?.required.filter(name => !existsSync(path.join(serverRoot, name))) ?? []
56
- const count = includes
57
- ? [...includes.required, ...includes.optional]
58
- .filter(name => existsSync(path.join(serverRoot, name)))
59
- .reduce((total, name) => total + countFiles(path.join(serverRoot, name)), 0)
60
- : countFiles(serverRoot)
61
- if (count > MAX_FILE_COUNT) {
62
- throw new CliError(`Packed server artifact ${serverRoot} would contain ${count} files, exceeding the limit ${MAX_FILE_COUNT}. This count follows ${countSource}. Reduce files included by pack before submission.`)
76
+ for (const name of policy.required) {
77
+ if (!existsSync(path.join(serverRoot, name))) throw new CliError(`Server pack input is missing: ${path.join(serverRoot, name)}`)
78
+ if (policy.excludes && isServerPackExcluded(name, policy)) throw new CliError(`Server pack exclude policy matches required path: ${name}`)
63
79
  }
64
- if (missing.length) {
65
- console.warn(`[verify] static:server-artifact-file-count skipped missing required paths in ${serverRoot}: ${missing.join(', ')}.`)
80
+ const paths = policy.excludes
81
+ ? readdirSync(serverRoot).filter(name => !isServerPackExcluded(name, policy))
82
+ : [...policy.required, ...policy.optional].filter(name => existsSync(path.join(serverRoot, name)))
83
+ const count = paths.reduce((total, name) => total + countFiles(path.join(serverRoot, name), policy), 0)
84
+ if (count > MAX_FILE_COUNT) {
85
+ throw new CliError(`Packed server artifact ${serverRoot} would contain ${count} files, exceeding the limit ${MAX_FILE_COUNT}. This count follows the server pack policy. Reduce files included by pack before submission.`)
66
86
  }
67
- console.log(`[verify] static:server-artifact-file-count → ${missing.length ? 'PARTIAL' : 'OK'} (${count}/${MAX_FILE_COUNT}, ${serverRoot}).`)
68
- return { skipped: false, count, missing }
87
+ console.log(`[verify] static:server-artifact-file-count → OK (${count}/${MAX_FILE_COUNT}, ${serverRoot}).`)
88
+ return { skipped: false, count, missing: [] }
69
89
  }