@open-dy/vibecoding-cli 0.1.6-beta.0 → 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/package.json +3 -3
- package/release-provenance.json +4 -4
- package/src/commands/build.mjs +3 -7
- package/src/commands/db.mjs +0 -1
- package/src/commands/lint.mjs +11 -14
- package/src/commands/verify.mjs +22 -11
- package/src/core/runner.mjs +6 -27
- package/src/validators/dependencies.mjs +3 -1
- package/src/validators/feed-card.cjs +1 -1
- package/src/validators/routes.cjs +3 -4
- package/src/validators/server-artifact.mjs +2 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-dy/vibecoding-cli",
|
|
3
|
-
"version": "0.1.6-beta.
|
|
3
|
+
"version": "0.1.6-beta.2",
|
|
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.
|
|
47
|
-
"@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"
|
|
48
48
|
},
|
|
49
49
|
"peerDependenciesMeta": {
|
|
50
50
|
"@open-dy/vibecoding-sdk-taro": {
|
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-
|
|
7
|
+
"digest": "sha256-587b165dbeeaa840e46a80b55f9137550dda6aa5d3b61eb3e3873e1706a3bddb",
|
|
8
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/commands/build.mjs
CHANGED
|
@@ -24,7 +24,6 @@ export async function buildTaroFrontend(context, type, options = {}) {
|
|
|
24
24
|
cwd: context.taroRoot,
|
|
25
25
|
env: taroEnvironment(context.taroRoot, options.env),
|
|
26
26
|
label: `taro ${type} build`,
|
|
27
|
-
quiet: options.quiet,
|
|
28
27
|
})
|
|
29
28
|
if (type === 'tt' && !options.watch) {
|
|
30
29
|
writeWebConfig(path.join(context.taroRoot, 'dist-tt'))
|
|
@@ -46,7 +45,6 @@ export async function buildOpenCard(context, options = {}) {
|
|
|
46
45
|
...cardEnvironment,
|
|
47
46
|
}),
|
|
48
47
|
label: 'opencard tt build',
|
|
49
|
-
quiet: options.quiet,
|
|
50
48
|
})
|
|
51
49
|
if (!options.watch) {
|
|
52
50
|
writeWebConfig(path.join(root, options.env?.TT_OUTPUT_ROOT?.trim() || 'dist'))
|
|
@@ -64,7 +62,6 @@ export function buildServer(context, options = {}) {
|
|
|
64
62
|
cwd: context.taroRoot,
|
|
65
63
|
env: options.env,
|
|
66
64
|
label: 'server build',
|
|
67
|
-
quiet: options.quiet,
|
|
68
65
|
})
|
|
69
66
|
}
|
|
70
67
|
|
|
@@ -73,7 +70,6 @@ export function buildClosedDomainServer(context, options = {}) {
|
|
|
73
70
|
cwd: context.taroRoot,
|
|
74
71
|
env: options.env,
|
|
75
72
|
label: 'closed-domain-server build',
|
|
76
|
-
quiet: options.quiet,
|
|
77
73
|
})
|
|
78
74
|
}
|
|
79
75
|
|
|
@@ -94,7 +90,7 @@ export async function buildPackInputs(context, options = {}) {
|
|
|
94
90
|
if (context.hasOpenCard) {
|
|
95
91
|
tasks.push({ name: 'opencard', run: () => buildOpenCard(context, options) })
|
|
96
92
|
}
|
|
97
|
-
await runTasks(tasks, { label: 'pack build'
|
|
93
|
+
await runTasks(tasks, { label: 'pack build' })
|
|
98
94
|
}
|
|
99
95
|
|
|
100
96
|
export async function runBuild(context, options = {}) {
|
|
@@ -140,6 +136,6 @@ export async function runBuild(context, options = {}) {
|
|
|
140
136
|
if (context.hasOpenCard) {
|
|
141
137
|
tasks.push({ name: 'opencard', run: () => buildOpenCard(context, buildOptions) })
|
|
142
138
|
}
|
|
143
|
-
await runTasks(tasks, { label: 'build'
|
|
144
|
-
validateRoutes(context
|
|
139
|
+
await runTasks(tasks, { label: 'build' })
|
|
140
|
+
validateRoutes(context)
|
|
145
141
|
}
|
package/src/commands/db.mjs
CHANGED
package/src/commands/lint.mjs
CHANGED
|
@@ -106,21 +106,21 @@ export async function lintOpenCard(context, options = {}) {
|
|
|
106
106
|
{ name: 'opencard-structure', run: async () => validateOpenCardStructure(validationContext) },
|
|
107
107
|
{ name: 'opencard-widget-shell', run: async () => validateWidgetShell(validationContext) },
|
|
108
108
|
{ name: 'opencard-lynx-css', run: () => runLynxCss(root, options.lintProject) },
|
|
109
|
-
{ name: 'opencard-tsc', run: () => runProcess(tsc, ['--noEmit'], { cwd: root
|
|
109
|
+
{ name: 'opencard-tsc', run: () => runProcess(tsc, ['--noEmit'], { cwd: root }) },
|
|
110
110
|
]
|
|
111
111
|
if (eslintSelection.runEslint) {
|
|
112
112
|
tasks.push({
|
|
113
113
|
name: 'opencard-eslint',
|
|
114
|
-
run: () => runProcess(eslint, eslintArgs, { cwd: root
|
|
114
|
+
run: () => runProcess(eslint, eslintArgs, { cwd: root }),
|
|
115
115
|
})
|
|
116
116
|
}
|
|
117
|
-
await runTasks(tasks, { label: 'opencard lint'
|
|
117
|
+
await runTasks(tasks, { label: 'opencard lint' })
|
|
118
118
|
}
|
|
119
119
|
|
|
120
120
|
export async function runLint(context, options = {}) {
|
|
121
121
|
const incremental = options.incremental || process.env.LINT_INCREMENTAL === '1'
|
|
122
122
|
if (context.target === 'opencard') {
|
|
123
|
-
await lintOpenCard(context, { incremental
|
|
123
|
+
await lintOpenCard(context, { incremental })
|
|
124
124
|
return
|
|
125
125
|
}
|
|
126
126
|
|
|
@@ -142,8 +142,8 @@ export async function runLint(context, options = {}) {
|
|
|
142
142
|
const tasks = []
|
|
143
143
|
if (full || changed.some((file) => file.startsWith('src/'))) {
|
|
144
144
|
tasks.push(
|
|
145
|
-
{ name: 'taro-tsc', run: () => runProcess(tsc, ['--noEmit'], { cwd: context.root
|
|
146
|
-
{ name: 'taro-eslint', run: () => runProcess(eslint, eslintArgs, { cwd: context.root
|
|
145
|
+
{ name: 'taro-tsc', run: () => runProcess(tsc, ['--noEmit'], { cwd: context.root }) },
|
|
146
|
+
{ name: 'taro-eslint', run: () => runProcess(eslint, eslintArgs, { cwd: context.root }) },
|
|
147
147
|
)
|
|
148
148
|
}
|
|
149
149
|
if (full || serverChanged || closedDomainChanged) {
|
|
@@ -156,7 +156,7 @@ export async function runLint(context, options = {}) {
|
|
|
156
156
|
tasks.push({
|
|
157
157
|
name: 'server-lint',
|
|
158
158
|
run: () => runTasks([
|
|
159
|
-
{ name: 'server-tsc', run: () => runProcess(tsc, ['--noEmit'], { cwd: context.serverRoot
|
|
159
|
+
{ name: 'server-tsc', run: () => runProcess(tsc, ['--noEmit'], { cwd: context.serverRoot }) },
|
|
160
160
|
{
|
|
161
161
|
name: 'server-eslint',
|
|
162
162
|
run: () => runProcess(eslint, [
|
|
@@ -164,16 +164,13 @@ export async function runLint(context, options = {}) {
|
|
|
164
164
|
'--cache-location',
|
|
165
165
|
'node_modules/.cache/eslint/server-src',
|
|
166
166
|
'src/**/*.{js,jsx,ts,tsx}',
|
|
167
|
-
], { cwd: context.serverRoot
|
|
167
|
+
], { cwd: context.serverRoot }),
|
|
168
168
|
},
|
|
169
|
-
], { label: 'server lint'
|
|
169
|
+
], { label: 'server lint' }),
|
|
170
170
|
})
|
|
171
171
|
}
|
|
172
172
|
if (context.hasOpenCard && (full || changed.some((file) => file.startsWith('opencard/')))) {
|
|
173
|
-
tasks.push({
|
|
174
|
-
name: 'opencard-lint',
|
|
175
|
-
run: () => lintOpenCard(context, { incremental: !full, quiet: options.quiet }),
|
|
176
|
-
})
|
|
173
|
+
tasks.push({ name: 'opencard-lint', run: () => lintOpenCard(context, { incremental: !full }) })
|
|
177
174
|
}
|
|
178
|
-
await runTasks(tasks, { label: 'lint'
|
|
175
|
+
await runTasks(tasks, { label: 'lint' })
|
|
179
176
|
}
|
package/src/commands/verify.mjs
CHANGED
|
@@ -33,23 +33,21 @@ async function runStatic(context) {
|
|
|
33
33
|
validateDependencies(context)
|
|
34
34
|
const env = await readRuntimeEnvironment(context.root)
|
|
35
35
|
const tasks = [
|
|
36
|
-
{ name: 'lint', run: () => runLint(context
|
|
37
|
-
{ name: 'build', run: () => runBuild(context, { frontendTarget: 'tt'
|
|
36
|
+
{ name: 'lint', run: () => runLint(context) },
|
|
37
|
+
{ name: 'build', run: () => runBuild(context, { frontendTarget: 'tt' }) },
|
|
38
38
|
]
|
|
39
39
|
if (context.target === 'taro') {
|
|
40
40
|
tasks.push(
|
|
41
|
-
{ name: 'database', run: () => runDatabaseInit(context, { env
|
|
42
|
-
{ name: 'feed-card', run: async () => validateFeedCard(context
|
|
41
|
+
{ name: 'database', run: () => runDatabaseInit(context, { env }) },
|
|
42
|
+
{ name: 'feed-card', run: async () => validateFeedCard(context) },
|
|
43
43
|
)
|
|
44
44
|
}
|
|
45
|
-
await runTasks(tasks, { label: 'static verification'
|
|
45
|
+
await runTasks(tasks, { label: 'static verification' })
|
|
46
46
|
if (context.target === 'taro') {
|
|
47
47
|
validateServerArtifactFileCount(context.serverRoot ?? path.join(context.root, 'server'), {
|
|
48
48
|
packScriptPath: path.join(context.root, 'scripts/pack.sh'),
|
|
49
|
-
quiet: true,
|
|
50
49
|
})
|
|
51
50
|
}
|
|
52
|
-
console.log('[verify] Static phase passed (all parallel tasks OK).')
|
|
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) {
|
|
@@ -626,30 +625,42 @@ export async function runVerify(context, options = {}) {
|
|
|
626
625
|
const frontendUrl = resolveFrontendUrl(context, options)
|
|
627
626
|
parseHttpUrl(frontendUrl, 'frontend')
|
|
628
627
|
if (full || options.staticOnly) {
|
|
628
|
+
console.log('[verify] === static ===')
|
|
629
629
|
await runStatic(context)
|
|
630
|
+
console.log('[verify] Static phase passed (all parallel tasks OK).')
|
|
630
631
|
}
|
|
631
632
|
if (options.staticOnly) {
|
|
632
633
|
console.log('[verify] --static-only: done.')
|
|
633
634
|
return
|
|
634
635
|
}
|
|
635
636
|
|
|
637
|
+
console.log('[verify] === dynamic: ports ===')
|
|
636
638
|
await verifyPorts(context, options, frontendUrl)
|
|
639
|
+
console.log(`[verify] Ports ready: ${targetPorts(context, frontendUrl, full).map(({ label, port }) => `${label}:${port}`).join(', ')}.`)
|
|
637
640
|
if (options.portsOnly) {
|
|
638
641
|
console.log('[verify] --ports-only: done.')
|
|
642
|
+
console.log('[verify] All checks passed.')
|
|
639
643
|
return
|
|
640
644
|
}
|
|
641
645
|
if (full && context.target === 'taro') {
|
|
642
|
-
|
|
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).')
|
|
643
651
|
}
|
|
652
|
+
console.log('[verify] === dynamic: browser runtime ===')
|
|
644
653
|
if (context.target === 'taro' &&
|
|
645
654
|
existsSync(path.resolve(context.root, '../sar_game/package.json'))) {
|
|
646
655
|
console.log('[vibecoding:verify] SAR game project detected; skipping browser runtime checks')
|
|
647
656
|
console.log('[verify] All checks passed.')
|
|
648
657
|
return
|
|
649
658
|
}
|
|
650
|
-
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}.`)
|
|
651
661
|
if (context.target === 'taro' && context.hasOpenCard) {
|
|
652
|
-
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/.')
|
|
653
664
|
}
|
|
654
665
|
console.log('[verify] All checks passed.')
|
|
655
666
|
}
|
package/src/core/runner.mjs
CHANGED
|
@@ -12,49 +12,28 @@ export function resolveSdkTaroEntry(root) {
|
|
|
12
12
|
return createRequire(path.join(root, 'package.json')).resolve('@open-dy/vibecoding-sdk-opencard/build/taro-toolchain')
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
function withCapturedOutput(message, stdout, stderr) {
|
|
16
|
-
if (!stdout && !stderr) return message
|
|
17
|
-
const separator = stdout && stderr && !stdout.endsWith('\n') ? '\n' : ''
|
|
18
|
-
return `${message}\n${stdout}${separator}${stderr}`
|
|
19
|
-
}
|
|
20
|
-
|
|
21
15
|
export function runProcess(command, args = [], options = {}) {
|
|
22
16
|
const label = options.label ?? path.basename(command)
|
|
23
17
|
return new Promise((resolve, reject) => {
|
|
24
|
-
const captureOutput = options.quiet && options.stdio === undefined
|
|
25
18
|
const child = spawn(command, args, {
|
|
26
19
|
cwd: options.cwd,
|
|
27
20
|
env: { ...process.env, ...options.env },
|
|
28
|
-
stdio:
|
|
21
|
+
stdio: options.stdio ?? 'inherit',
|
|
29
22
|
detached: options.detached ?? false,
|
|
30
23
|
shell: options.shell ?? false,
|
|
31
24
|
})
|
|
32
|
-
let stdout = ''
|
|
33
|
-
let stderr = ''
|
|
34
|
-
if (captureOutput) {
|
|
35
|
-
child.stdout.setEncoding('utf8')
|
|
36
|
-
child.stderr.setEncoding('utf8')
|
|
37
|
-
child.stdout.on('data', (chunk) => { stdout += chunk })
|
|
38
|
-
child.stderr.on('data', (chunk) => { stderr += chunk })
|
|
39
|
-
}
|
|
40
25
|
|
|
41
26
|
child.on('error', (error) => {
|
|
42
|
-
reject(new CliError(
|
|
43
|
-
`${label} failed to start: ${error.message}`,
|
|
44
|
-
stdout,
|
|
45
|
-
stderr,
|
|
46
|
-
)))
|
|
27
|
+
reject(new CliError(`${label} failed to start: ${error.message}`))
|
|
47
28
|
})
|
|
48
29
|
child.on('exit', (code, signal) => {
|
|
49
30
|
if (code === 0) {
|
|
50
31
|
resolve({ code: 0, signal, child })
|
|
51
32
|
return
|
|
52
33
|
}
|
|
53
|
-
reject(new CliError(
|
|
34
|
+
reject(new CliError(
|
|
54
35
|
`${label} failed${signal ? ` with signal ${signal}` : ` with exit code ${code}`}`,
|
|
55
|
-
|
|
56
|
-
stderr,
|
|
57
|
-
)))
|
|
36
|
+
))
|
|
58
37
|
})
|
|
59
38
|
})
|
|
60
39
|
}
|
|
@@ -99,9 +78,9 @@ export function runCaptured(command, args = [], options = {}) {
|
|
|
99
78
|
export async function runTasks(tasks, options = {}) {
|
|
100
79
|
const results = await Promise.allSettled(
|
|
101
80
|
tasks.map(async ({ name, run }) => {
|
|
102
|
-
|
|
81
|
+
console.log(`[vibecoding] starting ${name}`)
|
|
103
82
|
await run()
|
|
104
|
-
|
|
83
|
+
console.log(`[vibecoding] completed ${name}`)
|
|
105
84
|
return name
|
|
106
85
|
}),
|
|
107
86
|
)
|
|
@@ -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
|
}
|
|
@@ -3,7 +3,6 @@ const path = require('path');
|
|
|
3
3
|
|
|
4
4
|
function validateRoutes(context, options = {}) {
|
|
5
5
|
if (context.target !== 'taro') return;
|
|
6
|
-
const quiet = Boolean(options.quiet);
|
|
7
6
|
const ROOT = context.root;
|
|
8
7
|
const routesDir = options.routesDir || '/tmp/routes';
|
|
9
8
|
const CLIENT_FILE = path.join(routesDir, 'client-routes.json');
|
|
@@ -403,7 +402,7 @@ function main() {
|
|
|
403
402
|
const closedDomainServerRoutes = loadClosedDomainServerRoutes();
|
|
404
403
|
const cardRoutingConfig = loadCardRoutingConfig();
|
|
405
404
|
|
|
406
|
-
|
|
405
|
+
console.log('=== 前后端路由一致性检测报告 ===\n');
|
|
407
406
|
|
|
408
407
|
assertOpenDomainServerDoesNotCallClosedDomainOpenAPI();
|
|
409
408
|
|
|
@@ -452,7 +451,7 @@ function main() {
|
|
|
452
451
|
}
|
|
453
452
|
}
|
|
454
453
|
|
|
455
|
-
|
|
454
|
+
console.log('--- 检测结果 ---\n');
|
|
456
455
|
|
|
457
456
|
if (mismatched.length > 0) {
|
|
458
457
|
console.error(`未匹配(${mismatched.length} 条):以下前端请求路径未匹配到所选服务域的服务端路由,请检查前端请求路径、服务域配置,或确认服务端已注册对应路由`);
|
|
@@ -461,7 +460,7 @@ function main() {
|
|
|
461
460
|
}
|
|
462
461
|
throw new Error('前后端路由一致性检测失败');
|
|
463
462
|
} else {
|
|
464
|
-
|
|
463
|
+
console.log('所有前端静态请求路径均已在服务端注册对应路由,检测通过。');
|
|
465
464
|
}
|
|
466
465
|
}
|
|
467
466
|
|
|
@@ -63,7 +63,7 @@ function countFiles(target, policy) {
|
|
|
63
63
|
(policy.excludes && isServerPackExcluded(name, policy) ? 0 : countFiles(path.join(target, name), policy)), 0)
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
export function validateServerArtifactFileCount(serverRoot, { packScriptPath
|
|
66
|
+
export function validateServerArtifactFileCount(serverRoot, { packScriptPath } = {}) {
|
|
67
67
|
if (!existsSync(serverRoot)) throw new CliError(`Server directory is missing: ${serverRoot}`)
|
|
68
68
|
let policy = DEFAULT_POLICY
|
|
69
69
|
if (packScriptPath) {
|
|
@@ -84,8 +84,6 @@ export function validateServerArtifactFileCount(serverRoot, { packScriptPath, qu
|
|
|
84
84
|
if (count > MAX_FILE_COUNT) {
|
|
85
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.`)
|
|
86
86
|
}
|
|
87
|
-
|
|
88
|
-
console.log(`[verify] static:server-artifact-file-count → OK (${count}/${MAX_FILE_COUNT}, ${serverRoot}).`)
|
|
89
|
-
}
|
|
87
|
+
console.log(`[verify] static:server-artifact-file-count → OK (${count}/${MAX_FILE_COUNT}, ${serverRoot}).`)
|
|
90
88
|
return { skipped: false, count, missing: [] }
|
|
91
89
|
}
|