@open-dy/vibecoding-cli 0.1.0 → 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
@@ -24,6 +24,12 @@ Turbo 构建应通过 CLI 包装命令执行,例如 `vibecoding turbo --target
24
24
 
25
25
  `vibecoding validate feed-card --target taro` 与 `verify` 的 Feed 卡片检查共享同一实现。它保留原模板脚本的校验:OpenCard 与 Feed 文件的存在关系、JSON 语法、`TemplateType=5` / `TemplateStyleType=6`、`APP_ID` / `OPEN_CARD_ID`、全部 Schema 错误,以及通过后尽力更新 `__timestamp`。失败时不改写 Feed 文件。
26
26
 
27
+ 兴趣卡的 Feed 缓存同步由 CLI 内部适配器负责:当 OpenCard 的 `build:raw.inputs` 声明
28
+ `.cache-inputs/feed-card.json` 时,启动 Turbo 前先将 `../feed/feed-card.json` 原样复制到该缓存文件;
29
+ 源文件缺失时清空缓存副本,同步失败则停止构建。原始 Feed 配置不被改写,其他 target 或未声明该输入的工程不执行同步。
30
+ 新模板仅调用 `vibecoding turbo --target opencard run build:raw`,需要与包含此能力的 CLI 版本一起交付。
31
+ 历史工程保留原有 `node scripts/sync-feed-card-input.mjs && …` 前缀也兼容,无需重装模板或覆盖业务配置。
32
+
27
33
  Schema 统一使用 `custom-card-schema.json`。`config/card-routing.json` 的 `cardData.serviceDomain`(`open` / `closed`)是卡片服务域的唯一来源;构建时会据此注入 OpenCard 常量。优先保留项目 `scripts/schemas/`、`feed/` 中的本地 Schema 约束,没有时使用 CLI 内置 Schema。内外场构建后的项目 Schema 仍受支持。
28
34
 
29
35
  两套模板的 `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`。
@@ -32,7 +38,7 @@ Schema 统一使用 `custom-card-schema.json`。`config/card-routing.json` 的 `
32
38
 
33
39
  服务包仅包含 `dist`、生成的 `Dockerfile`、`package.json`、`run.sh` 以及可选的 `sql`、`scripts`。生成 Dockerfile 时按原脚本规则为每条 `COPY package.json` 追加存在的目录,项目源文件不改写。构建/zip 失败保留退出码,临时目录在失败时也会清理。
34
40
 
35
- `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 的安装成功日志;自动启动预览后仍等待安装与端口就绪。
36
42
 
37
43
  浏览器验收优先使用 `agent-browser-real`,不可用时使用 `agent-browser`。浏览器命令只在验收阶段查找;CDP / 浏览器基础设施失败会报告 `infra_browser` 和 `runtime_status=unverified`,继续以非零退出码结束,页面与业务错误仍按实际错误判定。
38
44
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-dy/vibecoding-cli",
3
- "version": "0.1.0",
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": {
@@ -43,8 +43,8 @@
43
43
  "ajv": "^8.18.0"
44
44
  },
45
45
  "peerDependencies": {
46
- "@open-dy/vibecoding-sdk-taro": "0.1.0",
47
- "@open-dy/vibecoding-sdk-opencard": "0.1.0"
46
+ "@open-dy/vibecoding-sdk-taro": "0.1.0-beta.0",
47
+ "@open-dy/vibecoding-sdk-opencard": "0.1.0-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,12 +2,12 @@
2
2
  "schema": 1,
3
3
  "producer": "vibecoding-cli",
4
4
  "packageName": "@open-dy/vibecoding-cli",
5
- "version": "0.1.0",
5
+ "version": "0.1.1-beta.0",
6
6
  "source": {
7
- "digest": "sha256-07a273825a0a8e9dc91058343a3e733044e46875e3315aeabdc1611d73e27e66",
8
- "files": 83
7
+ "digest": "sha256-f70df961e49cdb9152f6141d4168736b82dd8095aa24ce90a40ce67b526039ab",
8
+ "files": 85
9
9
  },
10
- "payload": "sha256-788bb423cd6ed9ae6df0b3b25cd20eca43a28470e78eb13bef0fafc391e29c52",
10
+ "payload": "sha256-46821af7c87fc1f326c66d28ee378600f64cf1ee8fd1108a6303cc7ae64e9a90",
11
11
  "toolchain": {
12
12
  "node": "v22.22.3",
13
13
  "packageManager": null
@@ -0,0 +1,21 @@
1
+ import { copyFile, mkdir, writeFile } from 'node:fs/promises'
2
+ import { existsSync } from 'node:fs'
3
+ import path from 'node:path'
4
+
5
+ /**
6
+ * Prepare the Feed snapshot explicitly declared by interest-card templates.
7
+ * Keep the legacy source, destination and missing-file behavior: only the cache
8
+ * copy is written, never the project's Feed configuration.
9
+ */
10
+ export async function prepareFeedCardInput(projectRoot, config) {
11
+ if (!config.tasks?.['build:raw']?.inputs?.includes('.cache-inputs/feed-card.json')) return
12
+ const source = path.resolve(projectRoot, '../feed/feed-card.json')
13
+ const directory = path.join(projectRoot, '.cache-inputs')
14
+ const destination = path.join(directory, 'feed-card.json')
15
+ await mkdir(directory, { recursive: true })
16
+ if (existsSync(source)) {
17
+ await copyFile(source, destination)
18
+ } else {
19
+ await writeFile(destination, '', 'utf8')
20
+ }
21
+ }
@@ -0,0 +1,8 @@
1
+ import { prepareFeedCardInput } from '../adapters/interest-card/opencard/prepare-turbo-inputs.mjs'
2
+
3
+ /** Prepare target-specific inputs before Turbo computes any task/cache hashes. */
4
+ export async function prepareTurboInputs(context, config) {
5
+ if (context.target === 'opencard') {
6
+ await prepareFeedCardInput(context.root, config)
7
+ }
8
+ }
@@ -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 {
@@ -1,6 +1,7 @@
1
1
  import path from 'node:path'
2
2
  import { openCardBuildEnvironment } from '../build/opencard-environment.mjs'
3
- import { prepareTurboConfig } from '../build/turbo-config.mjs'
3
+ import { resolveTurboConfig, writeTurboConfig } from '../build/turbo-config.mjs'
4
+ import { prepareTurboInputs } from '../build/turbo-inputs.mjs'
4
5
  import { UsageError } from '../core/errors.mjs'
5
6
  import { resolveProjectBinary, runProcess } from '../core/runner.mjs'
6
7
 
@@ -25,7 +26,9 @@ export async function runTurbo(context, { args = [] } = {}) {
25
26
  throw new UsageError('--root-turbo-json is managed by vibecoding turbo and cannot be overridden')
26
27
  }
27
28
 
28
- const config = prepareTurboConfig({ projectRoot: context.root, target: context.target })
29
+ const resolvedConfig = resolveTurboConfig({ projectRoot: context.root, target: context.target })
30
+ await prepareTurboInputs(context, resolvedConfig)
31
+ const config = writeTurboConfig(context.root, resolvedConfig)
29
32
  const turboArgs = ['--root-turbo-json', path.relative(context.root, config), ...args]
30
33
  const environment = openCardBuildEnvironment(context)
31
34
  return runProcess(resolveProjectBinary(context.root, 'turbo'), turboArgs, {
@@ -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
  }