@open-dy/vibecoding-cli 0.1.0-beta.1 → 0.1.1-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
@@ -38,7 +38,7 @@ Schema 统一使用 `custom-card-schema.json`。`config/card-routing.json` 的 `
38
38
 
39
39
  服务包仅包含 `dist`、生成的 `Dockerfile`、`package.json`、`run.sh` 以及可选的 `sql`、`scripts`。生成 Dockerfile 时按原脚本规则为每条 `COPY package.json` 追加存在的目录,项目源文件不改写。构建/zip 失败保留退出码,临时目录在失败时也会清理。
40
40
 
41
- `verify` 的静态阶段在构建通过后,保留主工程 `server.zip` 的文件数预检(阈值 `1000`,超限失败)。标准 CLI 模板使用打包共用的清单;自定义 `scripts/pack.sh` 使用其 `REQUIRED_SERVER_INCLUDES` / `OPTIONAL_SERVER_INCLUDES`。脚本缺失或清单无法解析时,warning 并保守统计整个 `server/`;缺少目录或必需文件时明确报告未覆盖范围。检查范围不扩大到封闭域服务,不生成 ZIP,也不验证压缩包大小。静态检查不等待 dev 的安装成功日志;自动启动预览后仍等待安装与端口就绪。
41
+ `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
42
 
43
43
  浏览器验收优先使用 `agent-browser-real`,不可用时使用 `agent-browser`。浏览器命令只在验收阶段查找;CDP / 浏览器基础设施失败会报告 `infra_browser` 和 `runtime_status=unverified`,继续以非零退出码结束,页面与业务错误仍按实际错误判定。
44
44
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-dy/vibecoding-cli",
3
- "version": "0.1.0-beta.1",
3
+ "version": "0.1.1-beta.0",
4
4
  "description": "Command orchestration, validation and packaging for Douyin vibecoding Taro and OpenCard projects.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -2,12 +2,12 @@
2
2
  "schema": 1,
3
3
  "producer": "vibecoding-cli",
4
4
  "packageName": "@open-dy/vibecoding-cli",
5
- "version": "0.1.0-beta.1",
5
+ "version": "0.1.1-beta.0",
6
6
  "source": {
7
- "digest": "sha256-77d7b0ea1db52eb5a77054124658253f9fd7ad00337333b458128404d0553c84",
7
+ "digest": "sha256-f70df961e49cdb9152f6141d4168736b82dd8095aa24ce90a40ce67b526039ab",
8
8
  "files": 85
9
9
  },
10
- "payload": "sha256-95d7a467807d81da1884538f69e30a6682b617dde3b2df243777d59017b6052f",
10
+ "payload": "sha256-46821af7c87fc1f326c66d28ee378600f64cf1ee8fd1108a6303cc7ae64e9a90",
11
11
  "toolchain": {
12
12
  "node": "v22.22.3",
13
13
  "packageManager": null
@@ -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
  }