@open-dy/vibecoding-cli 0.1.5 → 0.1.6-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 +2 -2
- package/package.json +6 -5
- package/release-provenance.json +15 -0
- package/src/cli.mjs +8 -3
- package/src/commands/validate.mjs +5 -2
- package/src/commands/verify.mjs +2 -0
- package/src/core/args.mjs +2 -1
- package/src/validators/package-size.mjs +248 -0
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@ vibecoding verify --target taro --auto-start --full
|
|
|
16
16
|
- `taro`:Taro 主应用、Server,以及项目中存在时的 OpenCard。
|
|
17
17
|
- `opencard`:独立 OpenCard 项目。
|
|
18
18
|
|
|
19
|
-
主要命令:`dev`、`build`、`lint`、`verify`、`pack`、`docs`、`turbo`、`init-mysql-db`,以及 `validate feed-card|routes|empty|opencard-structure`。
|
|
19
|
+
主要命令:`dev`、`build`、`lint`、`verify`、`pack`、`docs`、`turbo`、`init-mysql-db`,以及 `validate feed-card|routes|empty|opencard-structure|package-size`。
|
|
20
20
|
|
|
21
21
|
完整 SDK API 文档随 SDK npm 包和 Skill 交付,优先按 `${SKILL_DIR}/sdk-docs/versions.json` 核对版本,再查对应端的 `llms.txt`。`vibecoding docs --target taro`(OpenCard 使用 `--target opencard`,可指定 `--cwd <工程目录>`)只读定位实际安装的 SDK,输出 JSON:依赖键、版本、包位置、包内文档索引及公开类型路径。`ready` 表示包内存在文档索引;`docs-missing` 表示已安装的包没有内置索引,继续查 Skill 文档,版本不一致或信息不足时按 `exports` / `types` 查公开入口。业务导入使用 `imports`,npm 别名以依赖键为前缀,不能换成包的 `name`。包尚不可用时最多等待 `--timeout` 秒(默认 90),超时返回 `SDK_NOT_INSTALLED`,应检查工程安装结果。命令不会安装、重建 SDK 或启动服务。
|
|
22
22
|
|
|
@@ -40,7 +40,7 @@ CLI 内置 Schema 与通用小程序模板支持可选 `BgMusicUrl`;填写时
|
|
|
40
40
|
|
|
41
41
|
服务包仅包含 `dist`、生成的 `Dockerfile`、`package.json`、`run.sh` 以及可选的 `sql`、`scripts`。生成 Dockerfile 时按原脚本规则为每条 `COPY package.json` 追加存在的目录,项目源文件不改写。构建/zip 失败保留退出码,临时目录在失败时也会清理。
|
|
42
42
|
|
|
43
|
-
`verify` 的静态阶段在构建通过后,校验主工程 `server.zip` 的文件数(阈值 `1000`,超限失败),不生成 ZIP
|
|
43
|
+
`verify` 的静态阶段在构建通过后,校验主工程 `server.zip` 的文件数(阈值 `1000`,超限失败),不生成 ZIP 或计算服务端压缩包大小。随后对 tt 产物跑 `tma project-size`:落地页 `dist-tt`,存在 OpenCard 时再查 `opencard/dist`,独立 `--target opencard` 查该工程 `dist`。限额为总包 16MB。`tma` 不在 PATH 时安装 `tt-ide-cli@0.1.34`。单独复检用 `vibecoding validate package-size --target taro`,要求产物已存在;`--json` 输出判定结果,`--tma <path>` 指定二进制。标准 CLI 模板与打包共享黑名单:任意层级过滤依赖、缓存、测试目录、环境文件和私钥,保留其余业务资源。自定义 `scripts/pack.sh` 支持 `REQUIRED_SERVER_PATHS` / `SERVER_PACK_EXCLUDES` / `SERVER_PACK_EXCLUDE_GLOBS`,兼容旧的 `REQUIRED_SERVER_INCLUDES` / `OPTIONAL_SERVER_INCLUDES`;脚本缺失或无法解析、服务目录或必需文件缺失均失败。`pack` 对普通和封闭域服务应用同一规则,并重写各自的 `.dockerignore`,使 ZIP 与镜像包含相同资源。静态检查不等待 dev 的安装成功日志;自动启动预览后仍等待安装与端口就绪。
|
|
44
44
|
|
|
45
45
|
浏览器验收优先使用 `agent-browser-real`,不可用时使用 `agent-browser`。浏览器命令只在验收阶段查找;CDP / 浏览器基础设施失败会报告 `infra_browser` 和 `runtime_status=unverified`,继续以非零退出码结束,页面与业务错误仍按实际错误判定。
|
|
46
46
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-dy/vibecoding-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6-beta.1",
|
|
4
4
|
"description": "Command orchestration, validation and packaging for Douyin vibecoding Taro and OpenCard projects.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -24,7 +24,8 @@
|
|
|
24
24
|
"bin",
|
|
25
25
|
"src",
|
|
26
26
|
"assets",
|
|
27
|
-
"README.md"
|
|
27
|
+
"README.md",
|
|
28
|
+
"release-provenance.json"
|
|
28
29
|
],
|
|
29
30
|
"scripts": {
|
|
30
31
|
"ci:check": "pnpm run build && pnpm run test",
|
|
@@ -42,8 +43,8 @@
|
|
|
42
43
|
"ajv": "^8.18.0"
|
|
43
44
|
},
|
|
44
45
|
"peerDependencies": {
|
|
45
|
-
"@open-dy/vibecoding-sdk-taro": "
|
|
46
|
-
"@open-dy/vibecoding-sdk-opencard": "0.1.
|
|
46
|
+
"@open-dy/vibecoding-sdk-taro": "0.1.0-beta.0",
|
|
47
|
+
"@open-dy/vibecoding-sdk-opencard": "0.1.3-beta.0"
|
|
47
48
|
},
|
|
48
49
|
"peerDependenciesMeta": {
|
|
49
50
|
"@open-dy/vibecoding-sdk-taro": {
|
|
@@ -59,7 +60,7 @@
|
|
|
59
60
|
"license": "UNLICENSED",
|
|
60
61
|
"publishConfig": {
|
|
61
62
|
"registry": "https://registry.npmjs.org/",
|
|
62
|
-
"tag": "
|
|
63
|
+
"tag": "beta",
|
|
63
64
|
"access": "public"
|
|
64
65
|
}
|
|
65
66
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema": 1,
|
|
3
|
+
"producer": "vibecoding-cli",
|
|
4
|
+
"packageName": "@open-dy/vibecoding-cli",
|
|
5
|
+
"version": "0.1.6-beta.1",
|
|
6
|
+
"source": {
|
|
7
|
+
"digest": "sha256-ff1ee2d57d7028ec10a7b6e38ff6fa605a076d665d8667ee0c168564b3f7d57e",
|
|
8
|
+
"files": 88
|
|
9
|
+
},
|
|
10
|
+
"payload": "sha256-fdb8c0900115ffe7138745c6b71545dca209f4a2053a881de779fe2da78f1912",
|
|
11
|
+
"toolchain": {
|
|
12
|
+
"node": "v22.23.1",
|
|
13
|
+
"packageManager": null
|
|
14
|
+
}
|
|
15
|
+
}
|
package/src/cli.mjs
CHANGED
|
@@ -20,7 +20,7 @@ Commands:
|
|
|
20
20
|
build Build the selected target
|
|
21
21
|
lint Type-check, lint and validate source structure
|
|
22
22
|
verify Run static and/or runtime verification
|
|
23
|
-
validate <name> Run feed-card, routes, empty or
|
|
23
|
+
validate <name> Run feed-card, routes, empty, opencard-structure or package-size
|
|
24
24
|
init-mysql-db Initialize the Taro server database
|
|
25
25
|
pack Build deployment archives
|
|
26
26
|
docs Locate the selected project's installed SDK reference
|
|
@@ -41,7 +41,9 @@ Options:
|
|
|
41
41
|
--ports-only Verify listening ports only
|
|
42
42
|
--auto-start Start dev once when verify finds missing ports
|
|
43
43
|
--timeout <seconds> Dev startup / docs dependency wait timeout (default: 90)
|
|
44
|
-
--frontend-url <url> Override the primary runtime-check URL
|
|
44
|
+
--frontend-url <url> Override the primary runtime-check URL
|
|
45
|
+
--json Emit JSON from validate package-size
|
|
46
|
+
--tma <path> Use an explicit tma binary for package-size`
|
|
45
47
|
|
|
46
48
|
function assertNoExtra(positionals, count, command) {
|
|
47
49
|
if (positionals.length > count) throw new UsageError(`Unexpected argument for ${command}: ${positionals[count]}`)
|
|
@@ -101,7 +103,10 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
101
103
|
case 'init-mysql-db': assertNoExtra(positionals, 1, command); return runDatabaseInit(context)
|
|
102
104
|
case 'validate':
|
|
103
105
|
assertNoExtra(positionals, 2, command)
|
|
104
|
-
return runValidate(context, positionals[1]
|
|
106
|
+
return runValidate(context, positionals[1], {
|
|
107
|
+
json: Boolean(flags.json),
|
|
108
|
+
tmaBin: flags.tma,
|
|
109
|
+
})
|
|
105
110
|
default: throw new UsageError(`Unknown command: ${command}\n\n${HELP}`)
|
|
106
111
|
}
|
|
107
112
|
}
|
|
@@ -2,9 +2,10 @@ import { UsageError } from '../core/errors.mjs'
|
|
|
2
2
|
import { validateFeedCard } from '../validators/feed-card.mjs'
|
|
3
3
|
import { validateNonEmptyTaro } from '../validators/empty-project.mjs'
|
|
4
4
|
import { validateOpenCardStructure } from '../validators/opencard-structure.mjs'
|
|
5
|
+
import { validatePackageSize } from '../validators/package-size.mjs'
|
|
5
6
|
import { validateRoutes } from '../validators/routes.mjs'
|
|
6
7
|
|
|
7
|
-
export async function runValidate(context, validator) {
|
|
8
|
+
export async function runValidate(context, validator, options = {}) {
|
|
8
9
|
switch (validator) {
|
|
9
10
|
case 'feed-card':
|
|
10
11
|
validateFeedCard(context)
|
|
@@ -18,9 +19,11 @@ export async function runValidate(context, validator) {
|
|
|
18
19
|
case 'opencard-structure':
|
|
19
20
|
validateOpenCardStructure(context)
|
|
20
21
|
return
|
|
22
|
+
case 'package-size':
|
|
23
|
+
return validatePackageSize(context, options)
|
|
21
24
|
default:
|
|
22
25
|
throw new UsageError(
|
|
23
|
-
`Unknown validator: ${validator || '<missing>'}\nAvailable validators: feed-card, routes, empty, opencard-structure`,
|
|
26
|
+
`Unknown validator: ${validator || '<missing>'}\nAvailable validators: feed-card, routes, empty, opencard-structure, package-size`,
|
|
24
27
|
)
|
|
25
28
|
}
|
|
26
29
|
}
|
package/src/commands/verify.mjs
CHANGED
|
@@ -13,6 +13,7 @@ import { runBuild } from './build.mjs'
|
|
|
13
13
|
import { runDatabaseInit } from './db.mjs'
|
|
14
14
|
import { runLint } from './lint.mjs'
|
|
15
15
|
import { validateServerArtifactFileCount } from '../validators/server-artifact.mjs'
|
|
16
|
+
import { validatePackageSize } from '../validators/package-size.mjs'
|
|
16
17
|
|
|
17
18
|
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms))
|
|
18
19
|
const devLauncher = fileURLToPath(new URL('../../bin/vibecoding', import.meta.url))
|
|
@@ -48,6 +49,7 @@ async function runStatic(context) {
|
|
|
48
49
|
packScriptPath: path.join(context.root, 'scripts/pack.sh'),
|
|
49
50
|
})
|
|
50
51
|
}
|
|
52
|
+
await validatePackageSize(context)
|
|
51
53
|
}
|
|
52
54
|
|
|
53
55
|
export function startDevDetached(context, spawnProcess = spawn) {
|
package/src/core/args.mjs
CHANGED
|
@@ -9,10 +9,11 @@ const BOOLEAN_FLAGS = new Set([
|
|
|
9
9
|
'skip-install',
|
|
10
10
|
'skip-root-install',
|
|
11
11
|
'incremental',
|
|
12
|
+
'json',
|
|
12
13
|
'help',
|
|
13
14
|
])
|
|
14
15
|
|
|
15
|
-
const VALUE_FLAGS = new Set(['target', 'cwd', 'timeout', 'frontend-url', 'scope'])
|
|
16
|
+
const VALUE_FLAGS = new Set(['target', 'cwd', 'timeout', 'frontend-url', 'scope', 'tma'])
|
|
16
17
|
|
|
17
18
|
export function parseArgs(argv) {
|
|
18
19
|
const positionals = []
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process'
|
|
2
|
+
import { accessSync, constants, existsSync, mkdirSync, statSync } from 'node:fs'
|
|
3
|
+
import os from 'node:os'
|
|
4
|
+
import path from 'node:path'
|
|
5
|
+
import { CliError } from '../core/errors.mjs'
|
|
6
|
+
import { runCaptured } from '../core/runner.mjs'
|
|
7
|
+
|
|
8
|
+
const MB = 1024 * 1024
|
|
9
|
+
export const PACKAGE_SIZE_LIMITS = Object.freeze({
|
|
10
|
+
totalMax: 16 * MB,
|
|
11
|
+
})
|
|
12
|
+
export const TT_IDE_CLI_SPEC = 'tt-ide-cli@0.1.34'
|
|
13
|
+
const INSTALL_TIMEOUT_MS = 180_000
|
|
14
|
+
const TMA_TIMEOUT_MS = 60_000
|
|
15
|
+
|
|
16
|
+
export function formatPackageBytes(bytes) {
|
|
17
|
+
const value = Number(bytes) || 0
|
|
18
|
+
if (value < 1024) return `${value} B`
|
|
19
|
+
if (value < MB) return `${(value / 1024).toFixed(1)} KB`
|
|
20
|
+
return `${(value / MB).toFixed(2)} MB`
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function extractJsonObject(text) {
|
|
24
|
+
const raw = String(text || '')
|
|
25
|
+
const start = raw.indexOf('{')
|
|
26
|
+
const end = raw.lastIndexOf('}')
|
|
27
|
+
if (start < 0 || end <= start) return null
|
|
28
|
+
return raw.slice(start, end + 1)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function parseProjectSizeOutput(stdout, stderr = '') {
|
|
32
|
+
const combined = `${stdout || ''}\n${stderr || ''}`
|
|
33
|
+
if (/Bad Project Type/i.test(combined)) {
|
|
34
|
+
throw new CliError(
|
|
35
|
+
'tma project-size reported Bad Project Type: run it on tt build artifacts (dist-tt or opencard/dist), not source',
|
|
36
|
+
)
|
|
37
|
+
}
|
|
38
|
+
const jsonText = extractJsonObject(stdout) || extractJsonObject(stderr)
|
|
39
|
+
if (!jsonText) {
|
|
40
|
+
const preview = combined.replace(/\s+/g, ' ').trim().slice(0, 240)
|
|
41
|
+
throw new CliError(`Unable to parse tma project-size --json output${preview ? `: ${preview}` : ''}`)
|
|
42
|
+
}
|
|
43
|
+
let report
|
|
44
|
+
try {
|
|
45
|
+
report = JSON.parse(jsonText)
|
|
46
|
+
} catch {
|
|
47
|
+
throw new CliError(`Invalid tma project-size JSON: ${jsonText.slice(0, 240)}`)
|
|
48
|
+
}
|
|
49
|
+
if (!report || typeof report !== 'object' || typeof report.total !== 'number') {
|
|
50
|
+
throw new CliError(`tma project-size JSON is missing total: ${jsonText.slice(0, 240)}`)
|
|
51
|
+
}
|
|
52
|
+
return report
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function evaluateSizeReport(report, limits = PACKAGE_SIZE_LIMITS) {
|
|
56
|
+
const total = Number(report?.total) || 0
|
|
57
|
+
const failures = []
|
|
58
|
+
if (total > limits.totalMax) {
|
|
59
|
+
failures.push(`total package ${formatPackageBytes(total)} exceeds ${formatPackageBytes(limits.totalMax)}`)
|
|
60
|
+
}
|
|
61
|
+
return { ok: failures.length === 0, failures, total }
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function collectPackageSizeTargets(context) {
|
|
65
|
+
if (context.target === 'opencard') {
|
|
66
|
+
return [{
|
|
67
|
+
id: 'opencard',
|
|
68
|
+
label: 'OpenCard',
|
|
69
|
+
artifactRel: 'dist',
|
|
70
|
+
artifactDir: path.join(context.root, 'dist'),
|
|
71
|
+
}]
|
|
72
|
+
}
|
|
73
|
+
const targets = [{
|
|
74
|
+
id: 'landing-page',
|
|
75
|
+
label: 'landing page',
|
|
76
|
+
artifactRel: 'dist-tt',
|
|
77
|
+
artifactDir: path.join(context.root, 'dist-tt'),
|
|
78
|
+
}]
|
|
79
|
+
if (context.hasOpenCard && context.opencardRoot) {
|
|
80
|
+
targets.push({
|
|
81
|
+
id: 'interest-card',
|
|
82
|
+
label: 'interest card',
|
|
83
|
+
artifactRel: 'opencard/dist',
|
|
84
|
+
artifactDir: path.join(context.opencardRoot, 'dist'),
|
|
85
|
+
})
|
|
86
|
+
}
|
|
87
|
+
return targets
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function findOnPath(name, env = process.env) {
|
|
91
|
+
const executableName = process.platform === 'win32' ? `${name}.cmd` : name
|
|
92
|
+
for (const directory of (env.PATH || '').split(path.delimiter)) {
|
|
93
|
+
const executable = path.resolve(directory || '.', executableName)
|
|
94
|
+
try {
|
|
95
|
+
accessSync(executable, constants.X_OK)
|
|
96
|
+
if (statSync(executable).isFile()) return executable
|
|
97
|
+
} catch {}
|
|
98
|
+
}
|
|
99
|
+
return ''
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function resolveCacheRoot(env = process.env) {
|
|
103
|
+
if (existsSync('/workspace')) {
|
|
104
|
+
const workspaceTmp = '/workspace/tmp'
|
|
105
|
+
mkdirSync(workspaceTmp, { recursive: true })
|
|
106
|
+
return workspaceTmp
|
|
107
|
+
}
|
|
108
|
+
return env.TMPDIR || os.tmpdir()
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function resolveNpmBin() {
|
|
112
|
+
const sibling = path.join(path.dirname(process.execPath), process.platform === 'win32' ? 'npm.cmd' : 'npm')
|
|
113
|
+
if (existsSync(sibling)) return sibling
|
|
114
|
+
return findOnPath('npm') || 'npm'
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function resolveTmaBin(options = {}) {
|
|
118
|
+
if (options.tmaBin) {
|
|
119
|
+
if (!existsSync(options.tmaBin)) {
|
|
120
|
+
throw new CliError(`--tma path does not exist: ${options.tmaBin}`)
|
|
121
|
+
}
|
|
122
|
+
return options.tmaBin
|
|
123
|
+
}
|
|
124
|
+
const fromPath = findOnPath('tma', options.env)
|
|
125
|
+
if (fromPath) return fromPath
|
|
126
|
+
|
|
127
|
+
const cacheRoot = options.cacheRoot ?? resolveCacheRoot(options.env)
|
|
128
|
+
const prefix = path.join(cacheRoot, 'tt-ide-cli')
|
|
129
|
+
const bin = path.join(prefix, 'node_modules', '.bin', process.platform === 'win32' ? 'tma.cmd' : 'tma')
|
|
130
|
+
if (existsSync(bin)) return bin
|
|
131
|
+
|
|
132
|
+
mkdirSync(prefix, { recursive: true })
|
|
133
|
+
const npmCache = path.join(cacheRoot, 'npm-cache')
|
|
134
|
+
mkdirSync(npmCache, { recursive: true })
|
|
135
|
+
const env = {
|
|
136
|
+
...process.env,
|
|
137
|
+
...options.env,
|
|
138
|
+
npm_config_cache: npmCache,
|
|
139
|
+
npm_config_fund: 'false',
|
|
140
|
+
npm_config_audit: 'false',
|
|
141
|
+
npm_config_update_notifier: 'false',
|
|
142
|
+
}
|
|
143
|
+
const registries = [
|
|
144
|
+
env.NPM_CONFIG_REGISTRY,
|
|
145
|
+
env.npm_config_registry,
|
|
146
|
+
'https://bnpm.byted.org',
|
|
147
|
+
'https://registry.npmmirror.com/',
|
|
148
|
+
].filter((value, index, list) => value && list.indexOf(value) === index)
|
|
149
|
+
const npmBin = resolveNpmBin()
|
|
150
|
+
const errors = []
|
|
151
|
+
const runCommand = options.runCommand ?? ((command, args, spawnOptions) => spawnSync(command, args, spawnOptions))
|
|
152
|
+
for (const registry of registries) {
|
|
153
|
+
const result = runCommand(npmBin, ['install', TT_IDE_CLI_SPEC, '--prefix', prefix, '--registry', registry], {
|
|
154
|
+
env,
|
|
155
|
+
encoding: 'utf8',
|
|
156
|
+
timeout: INSTALL_TIMEOUT_MS,
|
|
157
|
+
})
|
|
158
|
+
if ((result.status === 0) && existsSync(bin)) return bin
|
|
159
|
+
const detail = String(result.stderr || result.stdout || `exit ${result.status}`).trim().slice(0, 400)
|
|
160
|
+
errors.push(`${registry}: ${detail}`)
|
|
161
|
+
}
|
|
162
|
+
throw new CliError(
|
|
163
|
+
`Unable to install ${TT_IDE_CLI_SPEC} for tma project-size. Confirm npm registry access, or put tma on PATH / pass --tma.\n${errors.join('\n')}`,
|
|
164
|
+
)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function artifactReady(artifactDir) {
|
|
168
|
+
return existsSync(path.join(artifactDir, 'app.json')) && (
|
|
169
|
+
existsSync(path.join(artifactDir, 'app.js')) || existsSync(path.join(artifactDir, 'app.ts'))
|
|
170
|
+
)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
async function defaultRunTma(tmaBin, artifactDir) {
|
|
174
|
+
return runCaptured(tmaBin, ['project-size', '--json', artifactDir], {
|
|
175
|
+
allowFailure: true,
|
|
176
|
+
timeoutMs: TMA_TIMEOUT_MS,
|
|
177
|
+
label: 'tma project-size',
|
|
178
|
+
})
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* @desc 校验 tt 构建产物包体积,必须对 dist-tt / opencard/dist 执行,不能对源码目录执行。
|
|
183
|
+
* @params {object} context CLI 工程上下文。
|
|
184
|
+
* @params {object} [options] 可选 tma 路径、JSON 输出和测试注入。
|
|
185
|
+
* @return {Promise<object>} 各产物的体积判定;超限或缺产物时抛出 CliError。
|
|
186
|
+
* @example await validatePackageSize(context)
|
|
187
|
+
*/
|
|
188
|
+
export async function validatePackageSize(context, options = {}) {
|
|
189
|
+
const targets = collectPackageSizeTargets(context)
|
|
190
|
+
const tmaBin = options.resolveTma
|
|
191
|
+
? await options.resolveTma()
|
|
192
|
+
: resolveTmaBin({ tmaBin: options.tmaBin, env: options.env, runCommand: options.runCommand })
|
|
193
|
+
const runTma = options.runTma ?? defaultRunTma
|
|
194
|
+
const results = []
|
|
195
|
+
|
|
196
|
+
for (const target of targets) {
|
|
197
|
+
const entry = { id: target.id, label: target.label, artifact: target.artifactRel, ok: false }
|
|
198
|
+
try {
|
|
199
|
+
if (!existsSync(target.artifactDir)) {
|
|
200
|
+
throw new CliError(
|
|
201
|
+
`Missing tt artifact directory ${target.artifactRel}. Run pnpm build:tt (or pnpm verify) first.`,
|
|
202
|
+
)
|
|
203
|
+
}
|
|
204
|
+
if (!artifactReady(target.artifactDir)) {
|
|
205
|
+
throw new CliError(
|
|
206
|
+
`${target.artifactRel} is not a complete tt artifact (missing app.json / app.js). Do not run tma project-size on source or H5 dist.`,
|
|
207
|
+
)
|
|
208
|
+
}
|
|
209
|
+
const output = await runTma(tmaBin, target.artifactDir)
|
|
210
|
+
const report = parseProjectSizeOutput(output?.stdout, output?.stderr)
|
|
211
|
+
const evaluated = evaluateSizeReport(report)
|
|
212
|
+
entry.total = evaluated.total
|
|
213
|
+
entry.ok = evaluated.ok
|
|
214
|
+
if (!evaluated.ok) {
|
|
215
|
+
entry.failures = evaluated.failures
|
|
216
|
+
throw new CliError(evaluated.failures.join('; '))
|
|
217
|
+
}
|
|
218
|
+
console.log(
|
|
219
|
+
`[vibecoding:package-size] OK ${target.label}: ${formatPackageBytes(evaluated.total)} / ${formatPackageBytes(PACKAGE_SIZE_LIMITS.totalMax)}`,
|
|
220
|
+
)
|
|
221
|
+
} catch (error) {
|
|
222
|
+
entry.ok = false
|
|
223
|
+
entry.message = error instanceof Error ? error.message : String(error)
|
|
224
|
+
console.error(`[vibecoding:package-size] FAIL ${target.label}: ${entry.message}`)
|
|
225
|
+
}
|
|
226
|
+
results.push(entry)
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const ok = results.length > 0 && results.every((item) => item.ok)
|
|
230
|
+
const payload = { ok, limits: PACKAGE_SIZE_LIMITS, tma: tmaBin, targets: results }
|
|
231
|
+
if (options.json) {
|
|
232
|
+
console.log(JSON.stringify({
|
|
233
|
+
ok: payload.ok,
|
|
234
|
+
limits: payload.limits,
|
|
235
|
+
targets: payload.targets,
|
|
236
|
+
}, null, 2))
|
|
237
|
+
}
|
|
238
|
+
if (!ok) {
|
|
239
|
+
const details = results
|
|
240
|
+
.filter((item) => !item.ok)
|
|
241
|
+
.map((item) => `${item.label} (${item.artifact}): ${item.message}`)
|
|
242
|
+
.join('\n')
|
|
243
|
+
throw new CliError(
|
|
244
|
+
`Package size check failed.\n${details}\nMove large images/audio/static assets to CDN, rebuild tt artifacts, then re-run.`,
|
|
245
|
+
)
|
|
246
|
+
}
|
|
247
|
+
return payload
|
|
248
|
+
}
|