@open-dy/vibecoding-cli 0.0.1-beta.3 → 0.0.1-beta.5
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
|
@@ -18,8 +18,6 @@ vibecoding verify --target taro --auto-start --full
|
|
|
18
18
|
|
|
19
19
|
主要命令:`dev`、`build`、`lint`、`verify`、`pack`、`docs`、`turbo`、`init-mysql-db`,以及 `validate feed-card|routes|empty|opencard-structure`。
|
|
20
20
|
|
|
21
|
-
`verify --full --auto-start` 在等待依赖安装日志前启动 dev;已有 dev 正在启动或运行时复用,一次校验最多启动一次。安装就绪只认最近一次启动的日志,安装失败直接报错;之后仍按静态检查、端口检查、API 与浏览器检查执行。`--static-only` 不启动 dev,也不等待 dev 日志。
|
|
22
|
-
|
|
23
21
|
`vibecoding docs --target taro`(OpenCard 使用 `--target opencard`,可指定 `--cwd <工程目录>`)只读定位实际安装的 SDK,输出 JSON:依赖键、版本、包位置、文档索引及公开类型路径。`ready` 时沿 `index` 查阅;`docs-missing` 表示已安装的旧包没有索引,按 `exports` / `types` 查公开入口。业务导入使用 `imports`,npm 别名以依赖键为前缀,不能换成包的 `name`。包尚不可用时最多等待 `--timeout` 秒(默认 90),超时返回 `SDK_NOT_INSTALLED`,应检查工程安装结果。命令不会安装、重建 SDK 或启动服务。
|
|
24
22
|
|
|
25
23
|
Turbo 构建应通过 CLI 包装命令执行,例如 `vibecoding turbo --target opencard run build:raw`。CLI 会读取项目的 `turbo.json`,合并版本化默认配置,并将结果写入项目内的 `.vibecoding/cache/turbo.json`;封闭域状态和 `config/card-routing.json` 也会参与构建环境与缓存输入。`--root-turbo-json` 由 CLI 管理,不能由业务命令覆盖;需要传递其他 Turbo 参数时,放在 `--` 后面。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-dy/vibecoding-cli",
|
|
3
|
-
"version": "0.0.1-beta.
|
|
3
|
+
"version": "0.0.1-beta.5",
|
|
4
4
|
"description": "Command orchestration, validation and packaging for Douyin vibecoding Taro and OpenCard projects.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
],
|
|
29
29
|
"scripts": {
|
|
30
30
|
"ci:check": "pnpm run build && pnpm run test",
|
|
31
|
-
"build": "node ./scripts/
|
|
31
|
+
"build": "node ./scripts/check-package-files.mjs",
|
|
32
32
|
"test": "node ../scripts/run-package-tests.mjs cli",
|
|
33
33
|
"pack:local": "pnpm pack --pack-destination ./artifacts",
|
|
34
34
|
"pack:ppe": "node ./scripts/pack-ppe.mjs",
|
|
@@ -42,8 +42,8 @@
|
|
|
42
42
|
"ajv": "^8.18.0"
|
|
43
43
|
},
|
|
44
44
|
"peerDependencies": {
|
|
45
|
-
"@open-dy/vibecoding-sdk-taro": "0.0.1-beta.
|
|
46
|
-
"@open-dy/vibecoding-sdk-opencard": "0.0.1-beta.
|
|
45
|
+
"@open-dy/vibecoding-sdk-taro": "0.0.1-beta.4",
|
|
46
|
+
"@open-dy/vibecoding-sdk-opencard": "0.0.1-beta.4"
|
|
47
47
|
},
|
|
48
48
|
"peerDependenciesMeta": {
|
|
49
49
|
"@open-dy/vibecoding-sdk-taro": {
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { spawn } from 'node:child_process';
|
|
3
3
|
import { startRoutingPreview } from '../../../preview/watch-card-routing.mjs';
|
|
4
|
+
import { loadPreviewRunner } from '../../../preview/preview-runner.mjs';
|
|
4
5
|
import fs from 'node:fs';
|
|
5
|
-
import { createRequire } from 'node:module';
|
|
6
6
|
import path from 'node:path';
|
|
7
|
-
import { fileURLToPath
|
|
7
|
+
import { fileURLToPath } from 'node:url';
|
|
8
8
|
|
|
9
9
|
const __dirname = path.join(process.env.VIBECODING_OPENCARD_ROOT || process.cwd(), 'scripts');
|
|
10
10
|
const root = path.resolve(__dirname, '..');
|
|
@@ -17,7 +17,6 @@ const META_PATH = '/opt/gem/ai_coding_app/.meta/ai_coding_app_meta.json';
|
|
|
17
17
|
const READY_TIMEOUT_MS = 5 * 60 * 1000;
|
|
18
18
|
const READY_POLL_MS = 500;
|
|
19
19
|
const DEFAULT_PORT = 10087;
|
|
20
|
-
const AI_CODING_BUILTIN_PACKAGES_DIR = process.env.AI_CODING_BUILTIN_PACKAGES_DIR;
|
|
21
20
|
|
|
22
21
|
const children = [];
|
|
23
22
|
let shuttingDown = false;
|
|
@@ -29,22 +28,6 @@ function log(message) {
|
|
|
29
28
|
console.log(`[opencard-dev-watch] ${message}`);
|
|
30
29
|
}
|
|
31
30
|
|
|
32
|
-
async function loadPreviewRunner() {
|
|
33
|
-
const manifest = JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8'));
|
|
34
|
-
const offlineRunner = manifest.devDependencies?.['@byted/microapp-preview-runner']?.startsWith('file:patches/') === true;
|
|
35
|
-
if (offlineRunner || !AI_CODING_BUILTIN_PACKAGES_DIR) {
|
|
36
|
-
return import(pathToFileURL(createRequire(path.join(root, 'package.json')).resolve('@byted/microapp-preview-runner')).href);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const requireFromTarget = createRequire(
|
|
40
|
-
path.join(path.resolve(AI_CODING_BUILTIN_PACKAGES_DIR), 'package.json'),
|
|
41
|
-
);
|
|
42
|
-
const runnerEntry = requireFromTarget.resolve('@byted/microapp-preview-runner');
|
|
43
|
-
|
|
44
|
-
log(`using preview runner from ${AI_CODING_BUILTIN_PACKAGES_DIR}`);
|
|
45
|
-
return import(pathToFileURL(runnerEntry).href);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
31
|
function readMeta() {
|
|
49
32
|
try {
|
|
50
33
|
return JSON.parse(fs.readFileSync(META_PATH, 'utf-8'));
|
|
@@ -151,7 +134,7 @@ for (const signal of ['SIGINT', 'SIGTERM', 'SIGHUP']) {
|
|
|
151
134
|
}
|
|
152
135
|
|
|
153
136
|
async function startCycle(env, signal) {
|
|
154
|
-
const { startRspuckPreview } = await loadPreviewRunner();
|
|
137
|
+
const { startRspuckPreview } = await loadPreviewRunner(root);
|
|
155
138
|
|
|
156
139
|
fs.rmSync(path.join(root, TT_OUTPUT_DIR), { recursive: true, force: true });
|
|
157
140
|
fs.rmSync(RSPUCK_OUTPUT_DIR, { recursive: true, force: true });
|
package/src/commands/verify.mjs
CHANGED
|
@@ -6,7 +6,6 @@ import { CliError } from '../core/errors.mjs'
|
|
|
6
6
|
import { readRuntimeEnvironment } from '../core/environment.mjs'
|
|
7
7
|
import { waitForPorts } from '../core/ports.mjs'
|
|
8
8
|
import { runCaptured, runTasks } from '../core/runner.mjs'
|
|
9
|
-
import { devProcessMatches, inspectDevProcess } from '../core/dev-process.mjs'
|
|
10
9
|
import { packageUses, validateDependencies } from '../validators/dependencies.mjs'
|
|
11
10
|
import { validateFeedCard } from '../validators/feed-card.mjs'
|
|
12
11
|
import { readCardRouting } from '../build/opencard-environment.mjs'
|
|
@@ -61,32 +60,14 @@ export function startDevDetached(context, spawnProcess = spawn) {
|
|
|
61
60
|
})
|
|
62
61
|
}
|
|
63
62
|
|
|
64
|
-
function
|
|
65
|
-
|
|
66
|
-
const
|
|
67
|
-
return env.VIBECODING_RUNTIME_ROOT || (
|
|
63
|
+
export function resolveDevLogPath(context, env = process.env) {
|
|
64
|
+
if (env.LOG_FILE) return env.LOG_FILE
|
|
65
|
+
const runtimeRoot = env.VIBECODING_RUNTIME_ROOT || (
|
|
68
66
|
process.platform === 'linux'
|
|
69
67
|
? '/workspace/tmp'
|
|
70
|
-
: path.join(root, '.tmp/microapp-dev')
|
|
68
|
+
: path.join(context.root, '.tmp/microapp-dev')
|
|
71
69
|
)
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
export function resolveDevLogPath(context, env = process.env) {
|
|
75
|
-
return env.LOG_FILE || path.join(resolveDevRuntimeRoot(context, env), 'microapp-logs/dev.log')
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
function readDevLog(context) {
|
|
79
|
-
try { return readFileSync(resolveDevLogPath(context), 'utf8') } catch { return '' }
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
function hasRunningDev(context) {
|
|
83
|
-
return ['microapp-dev-script.pid', 'microapp-dev-run.pid'].some(name => {
|
|
84
|
-
let pid
|
|
85
|
-
try { pid = readFileSync(path.join(resolveDevRuntimeRoot(context), name), 'utf8').trim() } catch { return false }
|
|
86
|
-
if (!/^[1-9][0-9]*$/.test(pid) || !Number.isSafeInteger(Number(pid))) return false
|
|
87
|
-
try { process.kill(Number(pid), 0) } catch (error) { if (error.code !== 'EPERM') return false }
|
|
88
|
-
return devProcessMatches(context, inspectDevProcess(Number(pid)))
|
|
89
|
-
})
|
|
70
|
+
return path.join(runtimeRoot, 'microapp-logs/dev.log')
|
|
90
71
|
}
|
|
91
72
|
|
|
92
73
|
function printLastRunErrors(logFile, log) {
|
|
@@ -108,15 +89,7 @@ export async function waitForDevInstall(context, timeoutSeconds, options = {}) {
|
|
|
108
89
|
const sleepFn = options.sleepFn ?? sleep
|
|
109
90
|
const deadline = Date.now() + timeoutSeconds * 1000
|
|
110
91
|
while (Date.now() < deadline) {
|
|
111
|
-
|
|
112
|
-
// appended its separator. A newly launched run supplies its starting offset.
|
|
113
|
-
const newLog = readLog().slice(options.logOffset ?? 0)
|
|
114
|
-
const sections = newLog.split('--- DEV_RUN_START ---')
|
|
115
|
-
const section = options.logOffset !== undefined && sections.length === 1 ? '' : sections.at(-1) || ''
|
|
116
|
-
const failure = section.split('\n').find(line =>
|
|
117
|
-
/(?:pnpm install failed|Dependency installation failed|CLI installation did not create)/i.test(line))
|
|
118
|
-
if (failure) throw new CliError(`Dev dependency installation failed: ${failure.trim()}. Full log: ${logFile}`)
|
|
119
|
-
if (section.includes('Dependencies installed successfully')) return
|
|
92
|
+
if (readLog().includes('Dependencies installed successfully')) return
|
|
120
93
|
await sleepFn(options.pollMs ?? 1_000)
|
|
121
94
|
}
|
|
122
95
|
printLastRunErrors(logFile, readLog())
|
|
@@ -126,25 +99,19 @@ export async function waitForDevInstall(context, timeoutSeconds, options = {}) {
|
|
|
126
99
|
)
|
|
127
100
|
}
|
|
128
101
|
|
|
129
|
-
async function prepareDev(context, options, frontendUrl) {
|
|
130
|
-
if (!options.autoStart || options.staticOnly) return {}
|
|
131
|
-
const ports = targetPorts(context, frontendUrl, options.full)
|
|
132
|
-
const status = await waitForPorts(ports, 1)
|
|
133
|
-
if (status.every(entry => entry.listening) || hasRunningDev(context)) return {}
|
|
134
|
-
const logOffset = readDevLog(context).length
|
|
135
|
-
const pid = await startDevDetached(context)
|
|
136
|
-
console.log(`[vibecoding:verify] started dev (PID ${pid}); waiting for dependency installation`)
|
|
137
|
-
return { logOffset }
|
|
138
|
-
}
|
|
139
|
-
|
|
140
102
|
async function verifyPorts(context, options, frontendUrl) {
|
|
141
103
|
const ports = targetPorts(context, frontendUrl, options.full)
|
|
142
|
-
|
|
104
|
+
let status = await waitForPorts(ports, 1)
|
|
105
|
+
if (status.every((entry) => entry.listening)) return
|
|
106
|
+
if (!options.autoStart) {
|
|
107
|
+
throw new CliError(`Ports not listening: ${status.filter((entry) => !entry.listening).map((entry) => `${entry.label}:${entry.port}`).join(', ')}`)
|
|
108
|
+
}
|
|
109
|
+
const pid = await startDevDetached(context)
|
|
110
|
+
console.log(`[vibecoding:verify] started dev (PID ${pid}); waiting for ports`)
|
|
111
|
+
await waitForDevInstall(context, options.timeoutSeconds)
|
|
112
|
+
status = await waitForPorts(ports, options.timeoutSeconds)
|
|
143
113
|
const missing = status.filter((entry) => !entry.listening)
|
|
144
114
|
if (missing.length > 0) {
|
|
145
|
-
if (!options.autoStart) {
|
|
146
|
-
throw new CliError(`Ports not listening: ${missing.map(entry => `${entry.label}:${entry.port}`).join(', ')}`)
|
|
147
|
-
}
|
|
148
115
|
printLastRunErrors(resolveDevLogPath(context))
|
|
149
116
|
throw new CliError(`Ports not ready after ${options.timeoutSeconds}s: ${missing.map((entry) => `${entry.label}:${entry.port}`).join(', ')}`)
|
|
150
117
|
}
|
|
@@ -619,8 +586,7 @@ export async function runVerify(context, options = {}) {
|
|
|
619
586
|
options = { timeoutSeconds: 90, ...options, full }
|
|
620
587
|
const frontendUrl = resolveFrontendUrl(context, options)
|
|
621
588
|
parseHttpUrl(frontendUrl, 'frontend')
|
|
622
|
-
|
|
623
|
-
if (full && !options.staticOnly) await waitForDevInstall(context, options.timeoutSeconds, installOptions)
|
|
589
|
+
if (full) await waitForDevInstall(context, options.timeoutSeconds)
|
|
624
590
|
if (full || options.staticOnly) await runStatic(context)
|
|
625
591
|
if (options.staticOnly) return
|
|
626
592
|
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs'
|
|
2
|
+
import { createRequire } from 'node:module'
|
|
3
|
+
import path from 'node:path'
|
|
4
|
+
import { pathToFileURL } from 'node:url'
|
|
5
|
+
import { CliError } from '../core/errors.mjs'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @desc 加载工程明确声明的预览运行时;未声明时优先使用沙箱内置运行时。
|
|
9
|
+
* @params {string} projectRoot 当前工程根目录。
|
|
10
|
+
* @params {object} env 运行环境,可通过 AI_CODING_BUILTIN_PACKAGES_DIR 指定内置包目录。
|
|
11
|
+
* @return {Promise<object>} 已加载的 Preview Runner 模块;所选包缺失时抛出错误。
|
|
12
|
+
* @example await loadPreviewRunner(projectRoot, process.env)
|
|
13
|
+
*/
|
|
14
|
+
export async function loadPreviewRunner(projectRoot, env = process.env) {
|
|
15
|
+
let declaredRunner = false
|
|
16
|
+
try {
|
|
17
|
+
const manifest = JSON.parse(readFileSync(path.join(projectRoot, 'package.json'), 'utf8'))
|
|
18
|
+
const specifier = manifest.devDependencies?.['@byted/microapp-preview-runner']
|
|
19
|
+
?? manifest.dependencies?.['@byted/microapp-preview-runner']
|
|
20
|
+
declaredRunner = typeof specifier === 'string' && specifier.trim().length > 0
|
|
21
|
+
} catch {}
|
|
22
|
+
const builtin = !declaredRunner && env.AI_CODING_BUILTIN_PACKAGES_DIR
|
|
23
|
+
const base = path.resolve(builtin || projectRoot)
|
|
24
|
+
const requireFromTarget = createRequire(path.join(base, 'package.json'))
|
|
25
|
+
let entry
|
|
26
|
+
try {
|
|
27
|
+
entry = requireFromTarget.resolve('@byted/microapp-preview-runner')
|
|
28
|
+
} catch {
|
|
29
|
+
throw new CliError(
|
|
30
|
+
`Cannot resolve @byted/microapp-preview-runner from ${base}. `
|
|
31
|
+
+ (declaredRunner ? 'Run pnpm install in the project.'
|
|
32
|
+
: 'Set AI_CODING_BUILTIN_PACKAGES_DIR or install it in the project.'),
|
|
33
|
+
)
|
|
34
|
+
}
|
|
35
|
+
console.log(`[preview-runner] using ${builtin ? 'builtin' : 'project'} package: ${entry}`)
|
|
36
|
+
return import(pathToFileURL(entry).href)
|
|
37
|
+
}
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { startRoutingPreview } from './watch-card-routing.mjs'
|
|
2
2
|
import { spawn } from 'node:child_process'
|
|
3
3
|
import { existsSync, mkdirSync, readFileSync, rmSync } from 'node:fs'
|
|
4
|
-
import { createRequire } from 'node:module'
|
|
5
4
|
import path from 'node:path'
|
|
6
|
-
import {
|
|
5
|
+
import { loadPreviewRunner } from './preview-runner.mjs'
|
|
7
6
|
import { CliError } from '../core/errors.mjs'
|
|
8
7
|
import { attachLabeledOutput } from '../core/process-output.mjs'
|
|
9
8
|
import { resolveSdkTaroEntry } from '../core/runner.mjs'
|
|
@@ -12,30 +11,12 @@ import { withServiceLogLabel } from '../core/service-log-context.mjs'
|
|
|
12
11
|
const META_PATH = '/opt/gem/ai_coding_app/.meta/ai_coding_app_meta.json'
|
|
13
12
|
const READY_TIMEOUT_MS = 5 * 60 * 1000
|
|
14
13
|
|
|
14
|
+
export { loadPreviewRunner } from './preview-runner.mjs'
|
|
15
|
+
|
|
15
16
|
export function conversationId() {
|
|
16
17
|
try { return JSON.parse(readFileSync(META_PATH, 'utf8')).conversation_id ?? '' } catch { return '' }
|
|
17
18
|
}
|
|
18
19
|
|
|
19
|
-
export async function loadPreviewRunner(projectRoot, env = process.env) {
|
|
20
|
-
// Only PPE's explicit offline dependency overrides the platform runtime.
|
|
21
|
-
let offlineRunner = false
|
|
22
|
-
try {
|
|
23
|
-
const manifest = JSON.parse(readFileSync(path.join(projectRoot, 'package.json'), 'utf8'))
|
|
24
|
-
offlineRunner = manifest.devDependencies?.['@byted/microapp-preview-runner']?.startsWith('file:patches/') === true
|
|
25
|
-
} catch {}
|
|
26
|
-
const base = offlineRunner ? projectRoot : env.AI_CODING_BUILTIN_PACKAGES_DIR || projectRoot
|
|
27
|
-
const requireFromTarget = createRequire(path.join(path.resolve(base), 'package.json'))
|
|
28
|
-
let entry
|
|
29
|
-
try {
|
|
30
|
-
entry = requireFromTarget.resolve('@byted/microapp-preview-runner')
|
|
31
|
-
} catch {
|
|
32
|
-
throw new CliError(
|
|
33
|
-
'Cannot resolve @byted/microapp-preview-runner. Set AI_CODING_BUILTIN_PACKAGES_DIR or install it in the project.',
|
|
34
|
-
)
|
|
35
|
-
}
|
|
36
|
-
return import(pathToFileURL(entry).href)
|
|
37
|
-
}
|
|
38
|
-
|
|
39
20
|
export function waitForFile(file, label, { signal, timeoutMs = READY_TIMEOUT_MS, pollMs = 500 } = {}) {
|
|
40
21
|
return new Promise((resolve, reject) => {
|
|
41
22
|
const deadline = Date.now() + timeoutMs
|
|
@@ -59,9 +40,8 @@ export function waitForFile(file, label, { signal, timeoutMs = READY_TIMEOUT_MS,
|
|
|
59
40
|
}
|
|
60
41
|
|
|
61
42
|
export function resolveOpenCardPreviewUrl(root, env = process.env, id = conversationId()) {
|
|
62
|
-
const configured = env.OPEN_CARD_PREVIEW_URL?.trim()
|
|
63
|
-
if (configured) return configured
|
|
64
43
|
if (!existsSync(path.join(root, 'opencard/package.json'))) return undefined
|
|
44
|
+
// Match the card service's port; the runner consumes this single target URL.
|
|
65
45
|
const localCardPreview = `http://127.0.0.1:${resolvePreviewPort(env.OPENCARD_PORT, 10087)}`
|
|
66
46
|
return id
|
|
67
47
|
? `${localCardPreview}/api/apps/open_ai_coding_chat_server/aio_fe/${id}/card_preview`
|
package/src/core/dev-process.mjs
DELETED
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import { execFileSync } from 'node:child_process'
|
|
2
|
-
import { readFileSync, readlinkSync, realpathSync } from 'node:fs'
|
|
3
|
-
import path from 'node:path'
|
|
4
|
-
import { resolveProjectContext } from './context.mjs'
|
|
5
|
-
|
|
6
|
-
/** Read only the recorded PID; never search processes or other projects. */
|
|
7
|
-
export function inspectDevProcess(pid) {
|
|
8
|
-
try {
|
|
9
|
-
const command = execFileSync('ps', ['-ww', '-p', String(pid), '-o', 'args='],
|
|
10
|
-
{ encoding: 'utf8', timeout: 1_000 }).trim()
|
|
11
|
-
const cwd = process.platform === 'linux' ? readlinkSync(`/proc/${pid}/cwd`)
|
|
12
|
-
: execFileSync('lsof', ['-a', '-p', String(pid), '-d', 'cwd', '-Fn'],
|
|
13
|
-
{ encoding: 'utf8', timeout: 1_000 }).split('\n').find(line => line.startsWith('n'))?.slice(1)
|
|
14
|
-
// Bootstrap may override its script's default root. Read just that field
|
|
15
|
-
// from Linux process metadata; never expose process environment contents.
|
|
16
|
-
let workspacePath
|
|
17
|
-
if (process.platform === 'linux') {
|
|
18
|
-
try { workspacePath = readFileSync(`/proc/${pid}/environ`, 'utf8')
|
|
19
|
-
.split('\0').find(entry => entry.startsWith('WORKSPACE_PATH='))?.slice('WORKSPACE_PATH='.length) } catch {}
|
|
20
|
-
}
|
|
21
|
-
return { command, cwd, workspacePath }
|
|
22
|
-
} catch { return null }
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const flag = (command, name) => command.match(new RegExp(
|
|
26
|
-
`(?:^|\\s)--${name}(?:=|\\s+)(.*?)(?=\\s+--[\\w-]+(?:[=\\s]|$)|$)`,
|
|
27
|
-
))?.[1]?.trim().replace(/^(['"])(.*)\1$/, '$2')
|
|
28
|
-
const canonical = directory => {
|
|
29
|
-
try { return realpathSync(directory) } catch { return path.resolve(directory) }
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/** A live PID is reusable only when its dev entry, project and target match. */
|
|
33
|
-
export function devProcessMatches(context, state) {
|
|
34
|
-
if (!state?.cwd) return false
|
|
35
|
-
const { command, cwd } = state
|
|
36
|
-
const launcher = /(?:^|\s)(?:\S*\/)?vibecoding(?:-dev|\.mjs)?\s/.test(command)
|
|
37
|
-
&& /(?:^|\s)dev(?:\s|$)/.test(command)
|
|
38
|
-
const bootstrap = command.match(/^(?:\S*\/)?bash\s+(.*?scripts\/dev_run\.sh)(?:\s|$)/)
|
|
39
|
-
if (!launcher && !bootstrap) return false
|
|
40
|
-
const target = launcher ? flag(command, 'target') : 'taro'
|
|
41
|
-
if (!['taro', 'opencard'].includes(target)) return false
|
|
42
|
-
let root = path.resolve(cwd, launcher ? flag(command, 'cwd') || '.'
|
|
43
|
-
: state.workspacePath || path.join(path.dirname(bootstrap[1]), '..'))
|
|
44
|
-
if (launcher) {
|
|
45
|
-
// The public CLI can start from a project subdirectory without --cwd.
|
|
46
|
-
try { root = resolveProjectContext({ cwd: root, target }).root } catch {}
|
|
47
|
-
}
|
|
48
|
-
root = canonical(root)
|
|
49
|
-
const expected = canonical(context.root)
|
|
50
|
-
return root === expected && target === context.target
|
|
51
|
-
|| context.target === 'opencard' && target === 'taro'
|
|
52
|
-
&& canonical(path.join(root, 'opencard')) === expected
|
|
53
|
-
}
|