@open-dy/vibecoding-cli 0.1.6-beta.1 → 0.1.6-beta.2
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 +1 -1
- package/release-provenance.json +5 -5
- package/src/cli.mjs +3 -8
- package/src/commands/validate.mjs +2 -5
- package/src/commands/verify.mjs +32 -9
- package/src/core/args.mjs +1 -2
- package/src/validators/dependencies.mjs +3 -1
- package/src/validators/package-size.mjs +0 -248
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`。
|
|
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 或计算压缩包大小。标准 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
package/release-provenance.json
CHANGED
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
"schema": 1,
|
|
3
3
|
"producer": "vibecoding-cli",
|
|
4
4
|
"packageName": "@open-dy/vibecoding-cli",
|
|
5
|
-
"version": "0.1.6-beta.
|
|
5
|
+
"version": "0.1.6-beta.2",
|
|
6
6
|
"source": {
|
|
7
|
-
"digest": "sha256-
|
|
8
|
-
"files":
|
|
7
|
+
"digest": "sha256-587b165dbeeaa840e46a80b55f9137550dda6aa5d3b61eb3e3873e1706a3bddb",
|
|
8
|
+
"files": 87
|
|
9
9
|
},
|
|
10
|
-
"payload": "sha256-
|
|
10
|
+
"payload": "sha256-4d87c25460989e6e84c14028f28268c0002b3badb3f71003510c5ba0a15ca1e0",
|
|
11
11
|
"toolchain": {
|
|
12
|
-
"node": "v22.
|
|
12
|
+
"node": "v22.22.3",
|
|
13
13
|
"packageManager": null
|
|
14
14
|
}
|
|
15
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
|
|
23
|
+
validate <name> Run feed-card, routes, empty or opencard-structure
|
|
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,9 +41,7 @@ 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
|
|
45
|
-
--json Emit JSON from validate package-size
|
|
46
|
-
--tma <path> Use an explicit tma binary for package-size`
|
|
44
|
+
--frontend-url <url> Override the primary runtime-check URL`
|
|
47
45
|
|
|
48
46
|
function assertNoExtra(positionals, count, command) {
|
|
49
47
|
if (positionals.length > count) throw new UsageError(`Unexpected argument for ${command}: ${positionals[count]}`)
|
|
@@ -103,10 +101,7 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
103
101
|
case 'init-mysql-db': assertNoExtra(positionals, 1, command); return runDatabaseInit(context)
|
|
104
102
|
case 'validate':
|
|
105
103
|
assertNoExtra(positionals, 2, command)
|
|
106
|
-
return runValidate(context, positionals[1]
|
|
107
|
-
json: Boolean(flags.json),
|
|
108
|
-
tmaBin: flags.tma,
|
|
109
|
-
})
|
|
104
|
+
return runValidate(context, positionals[1])
|
|
110
105
|
default: throw new UsageError(`Unknown command: ${command}\n\n${HELP}`)
|
|
111
106
|
}
|
|
112
107
|
}
|
|
@@ -2,10 +2,9 @@ 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'
|
|
6
5
|
import { validateRoutes } from '../validators/routes.mjs'
|
|
7
6
|
|
|
8
|
-
export async function runValidate(context, validator
|
|
7
|
+
export async function runValidate(context, validator) {
|
|
9
8
|
switch (validator) {
|
|
10
9
|
case 'feed-card':
|
|
11
10
|
validateFeedCard(context)
|
|
@@ -19,11 +18,9 @@ export async function runValidate(context, validator, options = {}) {
|
|
|
19
18
|
case 'opencard-structure':
|
|
20
19
|
validateOpenCardStructure(context)
|
|
21
20
|
return
|
|
22
|
-
case 'package-size':
|
|
23
|
-
return validatePackageSize(context, options)
|
|
24
21
|
default:
|
|
25
22
|
throw new UsageError(
|
|
26
|
-
`Unknown validator: ${validator || '<missing>'}\nAvailable validators: feed-card, routes, empty, opencard-structure
|
|
23
|
+
`Unknown validator: ${validator || '<missing>'}\nAvailable validators: feed-card, routes, empty, opencard-structure`,
|
|
27
24
|
)
|
|
28
25
|
}
|
|
29
26
|
}
|
package/src/commands/verify.mjs
CHANGED
|
@@ -13,7 +13,6 @@ 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'
|
|
17
16
|
|
|
18
17
|
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms))
|
|
19
18
|
const devLauncher = fileURLToPath(new URL('../../bin/vibecoding', import.meta.url))
|
|
@@ -49,7 +48,6 @@ async function runStatic(context) {
|
|
|
49
48
|
packScriptPath: path.join(context.root, 'scripts/pack.sh'),
|
|
50
49
|
})
|
|
51
50
|
}
|
|
52
|
-
await validatePackageSize(context)
|
|
53
51
|
}
|
|
54
52
|
|
|
55
53
|
export function startDevDetached(context, spawnProcess = spawn) {
|
|
@@ -550,7 +548,7 @@ export function isBrowserInfrastructureError(error) {
|
|
|
550
548
|
async function browserCheck(root, url, label) {
|
|
551
549
|
if (packageUses(root, '@open-dy/threejs')) {
|
|
552
550
|
console.log(`[vibecoding:verify] ${label}: skipping browser runtime check for @open-dy/threejs`)
|
|
553
|
-
return
|
|
551
|
+
return { skipped: true }
|
|
554
552
|
}
|
|
555
553
|
parseHttpUrl(url, label)
|
|
556
554
|
const session = process.env.AGENT_BROWSER_SESSION || `vibecoding-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`
|
|
@@ -606,6 +604,7 @@ async function browserCheck(root, url, label) {
|
|
|
606
604
|
console.log(`[vibecoding:verify] browser cleanup failed: ${error.message}`)
|
|
607
605
|
})
|
|
608
606
|
}
|
|
607
|
+
return { skipped: false }
|
|
609
608
|
}
|
|
610
609
|
|
|
611
610
|
export function resolveFrontendUrl(context, options = {}, env = process.env) {
|
|
@@ -625,19 +624,43 @@ export async function runVerify(context, options = {}) {
|
|
|
625
624
|
options = { timeoutSeconds: 90, ...options, full }
|
|
626
625
|
const frontendUrl = resolveFrontendUrl(context, options)
|
|
627
626
|
parseHttpUrl(frontendUrl, 'frontend')
|
|
628
|
-
if (full || options.staticOnly)
|
|
629
|
-
|
|
627
|
+
if (full || options.staticOnly) {
|
|
628
|
+
console.log('[verify] === static ===')
|
|
629
|
+
await runStatic(context)
|
|
630
|
+
console.log('[verify] Static phase passed (all parallel tasks OK).')
|
|
631
|
+
}
|
|
632
|
+
if (options.staticOnly) {
|
|
633
|
+
console.log('[verify] --static-only: done.')
|
|
634
|
+
return
|
|
635
|
+
}
|
|
630
636
|
|
|
637
|
+
console.log('[verify] === dynamic: ports ===')
|
|
631
638
|
await verifyPorts(context, options, frontendUrl)
|
|
632
|
-
|
|
633
|
-
if (
|
|
639
|
+
console.log(`[verify] Ports ready: ${targetPorts(context, frontendUrl, full).map(({ label, port }) => `${label}:${port}`).join(', ')}.`)
|
|
640
|
+
if (options.portsOnly) {
|
|
641
|
+
console.log('[verify] --ports-only: done.')
|
|
642
|
+
console.log('[verify] All checks passed.')
|
|
643
|
+
return
|
|
644
|
+
}
|
|
645
|
+
if (full && context.target === 'taro') {
|
|
646
|
+
console.log('[verify] === dynamic: feed API (/api/dy_card/data) ===')
|
|
647
|
+
const result = await verifyFeedApi(context)
|
|
648
|
+
console.log(result.skipped
|
|
649
|
+
? '[verify] No feed/feed-card.json; skipping /api/dy_card/data probe.'
|
|
650
|
+
: '[verify] /api/dy_card/data probe OK (success=true).')
|
|
651
|
+
}
|
|
652
|
+
console.log('[verify] === dynamic: browser runtime ===')
|
|
634
653
|
if (context.target === 'taro' &&
|
|
635
654
|
existsSync(path.resolve(context.root, '../sar_game/package.json'))) {
|
|
636
655
|
console.log('[vibecoding:verify] SAR game project detected; skipping browser runtime checks')
|
|
656
|
+
console.log('[verify] All checks passed.')
|
|
637
657
|
return
|
|
638
658
|
}
|
|
639
|
-
await browserCheck(context.root, frontendUrl, context.target)
|
|
659
|
+
const mainRuntime = await browserCheck(context.root, frontendUrl, context.target)
|
|
660
|
+
if (!mainRuntime.skipped) console.log(`[verify] No runtime issues at ${new URL(frontendUrl).href}.`)
|
|
640
661
|
if (context.target === 'taro' && context.hasOpenCard) {
|
|
641
|
-
await browserCheck(context.opencardRoot, 'http://localhost:10087/', 'opencard')
|
|
662
|
+
const cardRuntime = await browserCheck(context.opencardRoot, 'http://localhost:10087/', 'opencard')
|
|
663
|
+
if (!cardRuntime.skipped) console.log('[verify] No runtime issues at http://localhost:10087/.')
|
|
642
664
|
}
|
|
665
|
+
console.log('[verify] All checks passed.')
|
|
643
666
|
}
|
package/src/core/args.mjs
CHANGED
|
@@ -9,11 +9,10 @@ const BOOLEAN_FLAGS = new Set([
|
|
|
9
9
|
'skip-install',
|
|
10
10
|
'skip-root-install',
|
|
11
11
|
'incremental',
|
|
12
|
-
'json',
|
|
13
12
|
'help',
|
|
14
13
|
])
|
|
15
14
|
|
|
16
|
-
const VALUE_FLAGS = new Set(['target', 'cwd', 'timeout', 'frontend-url', 'scope'
|
|
15
|
+
const VALUE_FLAGS = new Set(['target', 'cwd', 'timeout', 'frontend-url', 'scope'])
|
|
17
16
|
|
|
18
17
|
export function parseArgs(argv) {
|
|
19
18
|
const positionals = []
|
|
@@ -22,7 +22,9 @@ export function validateDependencies(context) {
|
|
|
22
22
|
const manifest = JSON.parse(readFileSync(path.join(root, 'package.json'), 'utf8'))
|
|
23
23
|
for (const field of FIELDS) {
|
|
24
24
|
for (const name of FORBIDDEN) {
|
|
25
|
-
if (manifest[field]
|
|
25
|
+
if (manifest[field] && Object.hasOwn(manifest[field], name)) {
|
|
26
|
+
issues.push(`${path.join(root, 'package.json')}: ${field}.${name}`)
|
|
27
|
+
}
|
|
26
28
|
}
|
|
27
29
|
}
|
|
28
30
|
}
|
|
@@ -1,248 +0,0 @@
|
|
|
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
|
-
}
|