@mindstudio-ai/local-model-tunnel 0.5.27 → 0.5.28
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/dist/{chunk-N4J5ZGX6.js → chunk-JCY2MXIQ.js} +2 -2
- package/dist/{chunk-PH6M4GT4.js → chunk-MCYHE5LW.js} +3 -2
- package/dist/chunk-MCYHE5LW.js.map +1 -0
- package/dist/{chunk-JRLMRABX.js → chunk-WECAPRKN.js} +222 -22
- package/dist/chunk-WECAPRKN.js.map +1 -0
- package/dist/cli.js +1 -1
- package/dist/headless.js +2 -2
- package/dist/index.js +3 -3
- package/dist/{tui-ZFW3NSXF.js → tui-3K3QFK5W.js} +7 -6
- package/dist/{tui-ZFW3NSXF.js.map → tui-3K3QFK5W.js.map} +1 -1
- package/package.json +1 -1
- package/dist/chunk-JRLMRABX.js.map +0 -1
- package/dist/chunk-PH6M4GT4.js.map +0 -1
- /package/dist/{chunk-N4J5ZGX6.js.map → chunk-JCY2MXIQ.js.map} +0 -0
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
setProviderInstallPath,
|
|
8
8
|
submitProgress,
|
|
9
9
|
submitResult
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-WECAPRKN.js";
|
|
11
11
|
|
|
12
12
|
// src/providers/ollama/index.ts
|
|
13
13
|
import { Ollama } from "ollama";
|
|
@@ -1395,4 +1395,4 @@ export {
|
|
|
1395
1395
|
requestEvents,
|
|
1396
1396
|
TunnelRunner
|
|
1397
1397
|
};
|
|
1398
|
-
//# sourceMappingURL=chunk-
|
|
1398
|
+
//# sourceMappingURL=chunk-JCY2MXIQ.js.map
|
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
syncSchema,
|
|
23
23
|
watchConfigFile,
|
|
24
24
|
watchTableFiles
|
|
25
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-WECAPRKN.js";
|
|
26
26
|
|
|
27
27
|
// src/dev/ipc/ipc.ts
|
|
28
28
|
function emitEvent(event, data) {
|
|
@@ -338,6 +338,7 @@ async function startSession(cwd, opts, state, shutdown) {
|
|
|
338
338
|
branch,
|
|
339
339
|
methods: appConfig.methods.map((m) => ({ id: m.id, export: m.export, path: m.path }))
|
|
340
340
|
});
|
|
341
|
+
runner.setAppConfig(appConfig);
|
|
341
342
|
const session = await runner.start();
|
|
342
343
|
state.runner = runner;
|
|
343
344
|
initRequestLog(cwd);
|
|
@@ -524,4 +525,4 @@ async function startHeadless(opts = {}) {
|
|
|
524
525
|
export {
|
|
525
526
|
startHeadless
|
|
526
527
|
};
|
|
527
|
-
//# sourceMappingURL=chunk-
|
|
528
|
+
//# sourceMappingURL=chunk-MCYHE5LW.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/dev/ipc/ipc.ts","../src/dev/ipc/session-events.ts","../src/dev/stdin-commands/run-scenario.ts","../src/dev/stdin-commands/run-method.ts","../src/dev/stdin-commands/impersonate.ts","../src/dev/stdin-commands/browser.ts","../src/dev/stdin-commands/screenshot-full-page.ts","../src/dev/stdin-commands/dev-server-restarting.ts","../src/dev/stdin-commands/browser-status.ts","../src/dev/stdin-commands/reset-browser.ts","../src/dev/stdin-commands/index.ts","../src/headless.ts"],"sourcesContent":["/**\n * Central IPC module for headless mode.\n *\n * All stdout writes go through here. Two distinct message types:\n * - System events: unsolicited, no requestId (session lifecycle, connection, etc.)\n * - Command responses: always have requestId + status (started/completed)\n *\n * The caller distinguishes them by the presence of `requestId`.\n */\n\n/**\n * Emit a system event (no requestId).\n * Used for unsolicited events: session lifecycle, connection health, auth, etc.\n */\nexport function emitEvent(event: string, data?: Record<string, unknown>): void {\n process.stdout.write(JSON.stringify({ event, ...data }) + '\\n');\n}\n\n/**\n * Emit a command response (always has requestId).\n * Used for responses to stdin commands.\n */\nexport function emitResponse(\n action: string,\n requestId: string,\n status: 'started' | 'completed',\n data?: Record<string, unknown>,\n): void {\n process.stdout.write(\n JSON.stringify({ event: action, requestId, status, ...data }) + '\\n',\n );\n}\n","/**\n * Subscribe to DevRunner events and relay them as system events to stdout.\n * Only relays genuinely unsolicited events (poll-loop methods, connection, auth).\n * Command responses (scenarios, impersonation) are handled by stdin handlers directly.\n *\n * Returns an array of unsubscribe functions for cleanup on teardown.\n */\n\nimport { devRequestEvents } from './events';\nimport { emitEvent } from './ipc';\n\nexport function subscribeDevEvents(\n shutdown: () => Promise<void>,\n): Array<() => void> {\n const unsubs: Array<() => void> = [];\n\n // Platform-triggered method execution (poll loop)\n unsubs.push(\n devRequestEvents.onStart((event) => {\n emitEvent('platform-method-started', { id: event.id, method: event.method });\n }),\n );\n\n unsubs.push(\n devRequestEvents.onComplete((event) => {\n emitEvent('platform-method-completed', {\n id: event.id,\n success: event.success,\n duration: event.duration,\n ...(event.error ? { error: event.error } : {}),\n });\n }),\n );\n\n // Connection health\n unsubs.push(\n devRequestEvents.onConnectionWarning((message) => {\n emitEvent('connection-lost', { message });\n }),\n );\n\n unsubs.push(\n devRequestEvents.onConnectionRestored(() => {\n emitEvent('connection-restored');\n }),\n );\n\n // Session expiry\n unsubs.push(\n devRequestEvents.onSessionExpired(() => {\n emitEvent('session-expired');\n shutdown().then(() => process.exit(1));\n }),\n );\n\n // Auth refresh\n unsubs.push(\n devRequestEvents.onAuthRefreshStart((url) => {\n emitEvent('auth-refresh-start', { url });\n }),\n );\n\n unsubs.push(\n devRequestEvents.onAuthRefreshSuccess(() => {\n emitEvent('auth-refresh-success');\n }),\n );\n\n unsubs.push(\n devRequestEvents.onAuthRefreshFailed(() => {\n emitEvent('auth-refresh-failed');\n }),\n );\n\n return unsubs;\n}\n","import { detectAppConfig } from '../config/app-config';\nimport type { CommandContext } from './types';\n\nexport async function handleRunScenario(\n ctx: CommandContext,\n cmd: Record<string, unknown>,\n): Promise<Record<string, unknown>> {\n if (!ctx.state.runner) throw new Error('No active session');\n\n const freshConfig = detectAppConfig(ctx.cwd) ?? ctx.state.appConfig;\n const scenario = freshConfig?.scenarios.find((s) => s.id === cmd.scenarioId);\n if (!scenario) throw new Error(`Unknown scenario: ${cmd.scenarioId}`);\n\n const scenarioName = scenario.name ?? scenario.export;\n ctx.started({ scenarioId: scenario.id, name: scenarioName });\n\n const result = await ctx.state.runner.runScenario(scenario);\n\n return {\n success: result.success,\n scenarioId: scenario.id,\n name: scenarioName,\n ...(result.error ? { error: result.error } : {}),\n };\n}\n","import { detectAppConfig } from '../config/app-config';\nimport type { CommandContext } from './types';\n\nexport async function handleRunMethod(\n ctx: CommandContext,\n cmd: Record<string, unknown>,\n): Promise<Record<string, unknown>> {\n if (!ctx.state.runner) throw new Error('No active session');\n\n const methodName = cmd.method as string;\n if (!methodName) throw new Error('run-method requires \"method\" (export name or ID)');\n\n const freshConfig = detectAppConfig(ctx.cwd) ?? ctx.state.appConfig;\n const method =\n freshConfig?.methods.find((m) => m.export === methodName) ??\n freshConfig?.methods.find((m) => m.id === methodName);\n if (!method) throw new Error(`Unknown method: ${methodName}`);\n\n ctx.started({ method: method.export });\n\n const result = await ctx.state.runner.runMethod({\n methodExport: method.export,\n methodPath: method.path,\n input: cmd.input ?? {},\n });\n\n return {\n success: result.success,\n method: method.export,\n output: result.output ?? null,\n error: result.error ?? null,\n stdout: result.stdout ?? [],\n duration: result.duration,\n };\n}\n","import type { CommandContext } from './types';\n\nexport async function handleImpersonate(\n ctx: CommandContext,\n cmd: Record<string, unknown>,\n): Promise<Record<string, unknown>> {\n if (!ctx.state.runner) throw new Error('No active session');\n\n const roles = cmd.roles as string[];\n if (!Array.isArray(roles)) throw new Error('impersonate requires roles array');\n\n await ctx.state.runner.setImpersonation(roles);\n return { roles };\n}\n\nexport async function handleClearImpersonation(\n ctx: CommandContext,\n): Promise<Record<string, unknown>> {\n if (!ctx.state.runner) throw new Error('No active session');\n\n await ctx.state.runner.clearImpersonation();\n return { roles: null };\n}\n","import { getUploadUrl } from '../api';\nimport type { CommandContext } from './types';\n\nexport async function handleBrowser(\n ctx: CommandContext,\n cmd: Record<string, unknown>,\n): Promise<Record<string, unknown>> {\n if (!ctx.state.proxy) throw new Error('No active proxy — browser commands require a web interface');\n\n const steps = cmd.steps as Array<Record<string, unknown>>;\n if (!Array.isArray(steps) || steps.length === 0) {\n throw new Error('browser action requires a non-empty \"steps\" array');\n }\n\n // Inject upload details into any screenshotViewport steps so the browser\n // uploads directly to S3 instead of sending base64 over the WS connection.\n const preparedSteps = await injectScreenshotUploads(ctx, steps);\n\n const result = await ctx.state.proxy.dispatchBrowserCommand(preparedSteps);\n\n // Replace uploaded screenshot results with the public URL\n const resultSteps = (result.steps as Array<Record<string, unknown>>) ?? [];\n for (const step of resultSteps) {\n const stepResult = step.result as Record<string, unknown> | undefined;\n if (stepResult?.uploaded && stepResult?._publicUrl) {\n stepResult.url = stepResult._publicUrl;\n delete stepResult.uploaded;\n delete stepResult._publicUrl;\n delete stepResult.image;\n }\n }\n\n return {\n steps: resultSteps,\n snapshot: result.snapshot,\n logs: result.logs,\n duration: result.duration,\n };\n}\n\n/**\n * For each screenshotViewport step, get a presigned upload URL and attach it.\n * Non-screenshot steps are passed through unchanged.\n */\nasync function injectScreenshotUploads(\n ctx: CommandContext,\n steps: Array<Record<string, unknown>>,\n): Promise<Array<Record<string, unknown>>> {\n const session = ctx.state.runner?.getSession();\n const appId = ctx.state.appConfig?.appId;\n if (!session || !appId) return steps;\n\n const prepared: Array<Record<string, unknown>> = [];\n for (const step of steps) {\n if (step.command === 'screenshotViewport') {\n try {\n const { uploadUrl, uploadFields, publicUrl } = await getUploadUrl(\n appId,\n session.sessionId,\n 'jpg',\n 'image/jpeg',\n );\n prepared.push({ ...step, uploadUrl, uploadFields, _publicUrl: publicUrl });\n } catch {\n // If we can't get an upload URL, fall back to inline base64\n prepared.push(step);\n }\n } else {\n prepared.push(step);\n }\n }\n return prepared;\n}\n","import { getUploadUrl } from '../api';\nimport type { CommandContext } from './types';\n\nexport async function handleScreenshotFullPage(\n ctx: CommandContext,\n cmd: Record<string, unknown>,\n): Promise<Record<string, unknown>> {\n if (!ctx.state.proxy) throw new Error('No active proxy');\n if (!ctx.state.proxy.isBrowserConnected()) {\n throw new Error('No browser connected, please refresh the MindStudio preview');\n }\n if (!ctx.state.runner?.getSession() || !ctx.state.appConfig?.appId) {\n throw new Error('No active session');\n }\n\n const startTime = Date.now();\n\n // 1. Get presigned upload URL before dispatching to browser\n const session = ctx.state.runner.getSession()!;\n const { uploadUrl, uploadFields, publicUrl } = await getUploadUrl(\n ctx.state.appConfig.appId,\n session.sessionId,\n 'jpg',\n 'image/jpeg',\n );\n\n // 2. Dispatch to browser — always full-page with scroll-to-top\n const result = await ctx.state.proxy.dispatchBrowserCommand(\n [{ command: 'screenshotFullPage', uploadUrl, uploadFields }],\n 120_000,\n );\n\n const stepResult = (result.steps as Array<Record<string, unknown>>)?.[0]\n ?.result as { width: number; height: number; uploaded?: boolean } | undefined;\n\n if (!stepResult?.uploaded) {\n throw new Error('Screenshot capture or upload failed');\n }\n\n return {\n url: publicUrl,\n width: stepResult.width,\n height: stepResult.height,\n duration: Date.now() - startTime,\n };\n}\n","import type { CommandContext } from './types';\n\nexport async function handleDevServerRestarting(\n ctx: CommandContext,\n): Promise<Record<string, unknown>> {\n if (!ctx.state.proxy) throw new Error('No active proxy');\n\n ctx.state.proxy.markUpstreamDown();\n return {};\n}\n","import type { CommandContext } from './types';\n\nexport async function handleBrowserStatus(\n ctx: CommandContext,\n): Promise<Record<string, unknown>> {\n return { connected: ctx.state.proxy?.isBrowserConnected() ?? false };\n}\n","import type { CommandContext } from './types';\n\nexport async function handleResetBrowser(\n ctx: CommandContext,\n): Promise<Record<string, unknown>> {\n if (!ctx.state.proxy) throw new Error('No active proxy');\n if (!ctx.state.proxy.isBrowserConnected()) throw new Error('No browser connected');\n\n ctx.state.proxy.broadcastToClients('reload');\n return {};\n}\n","/**\n * Stdin command router for headless mode.\n *\n * Reads NDJSON commands from stdin and dispatches to individual handlers.\n * Every command must include a `requestId` for response correlation.\n * The router wraps handlers with automatic response framing.\n */\n\nimport { emitResponse } from '../ipc/ipc';\nimport { log } from '../logging/logger';\nimport { handleRunScenario } from './run-scenario';\nimport { handleRunMethod } from './run-method';\nimport { handleImpersonate, handleClearImpersonation } from './impersonate';\nimport { handleBrowser } from './browser';\nimport { handleScreenshotFullPage } from './screenshot-full-page';\nimport { handleDevServerRestarting } from './dev-server-restarting';\nimport { handleBrowserStatus } from './browser-status';\nimport { handleResetBrowser } from './reset-browser';\nimport type { SessionState, CommandContext, CommandHandler } from './types';\n\nexport type { SessionState } from './types';\n\nconst handlers: Record<string, CommandHandler> = {\n 'run-method': handleRunMethod,\n 'run-scenario': handleRunScenario,\n 'impersonate': handleImpersonate,\n 'clear-impersonation': handleClearImpersonation,\n 'browser': handleBrowser,\n 'screenshotFullPage': handleScreenshotFullPage,\n 'browser-status': handleBrowserStatus,\n 'reset-browser': handleResetBrowser,\n 'dev-server-restarting': handleDevServerRestarting,\n};\n\nexport function setupStdinCommands(\n state: SessionState,\n cwd: string,\n): void {\n if (!process.stdin.readable) return;\n\n let buffer = '';\n process.stdin.setEncoding('utf-8');\n process.stdin.on('data', (chunk: string) => {\n buffer += chunk;\n let idx: number;\n while ((idx = buffer.indexOf('\\n')) !== -1) {\n const line = buffer.slice(0, idx).trim();\n buffer = buffer.slice(idx + 1);\n if (!line) continue;\n\n let cmd: { action: string; requestId?: string; [key: string]: unknown };\n try {\n cmd = JSON.parse(line);\n } catch {\n log.warn('stdin', 'Invalid JSON on stdin', { preview: line.slice(0, 100) });\n continue;\n }\n\n handleStdinCommand(cmd, state, cwd);\n }\n });\n}\n\nasync function handleStdinCommand(\n cmd: { action: string; requestId?: string; [key: string]: unknown },\n state: SessionState,\n cwd: string,\n): Promise<void> {\n const { requestId, action } = cmd;\n\n if (!requestId) {\n log.warn('stdin', 'Command rejected: missing requestId', { action });\n return;\n }\n\n const handler = handlers[action];\n if (!handler) {\n emitResponse(action ?? 'unknown', requestId, 'completed', {\n success: false,\n error: `Unknown action: ${action}`,\n });\n return;\n }\n\n log.info('stdin', 'Command received', { requestId, action });\n\n const ctx: CommandContext = {\n state,\n cwd,\n requestId,\n started: (data) => emitResponse(action, requestId, 'started', data),\n };\n\n try {\n const result = await handler(ctx, cmd);\n log.info('stdin', 'Command complete', { requestId, action, success: result.success !== false });\n emitResponse(action, requestId, 'completed', result);\n } catch (err) {\n log.warn('stdin', 'Command failed', { requestId, action, error: err instanceof Error ? err.message : String(err) });\n emitResponse(action, requestId, 'completed', {\n success: false,\n error: err instanceof Error ? err.message : String(err),\n });\n }\n}\n","/**\n * Headless Dev Mode\n *\n * Runs the MindStudio dev tunnel without a TUI. Designed for programmatic\n * control by a parent process (e.g., a sandbox C&C server or CI pipeline).\n *\n * Outputs structured JSON events to stdout (one per line, newline-delimited).\n * The parent process reads these to track session state, method execution,\n * errors, and connection health.\n *\n * Does NOT start a dev server — the parent process manages that separately.\n * The tunnel just needs to know which port to proxy to.\n *\n * @module\n */\n\nimport { DevRunner } from './dev/execution/runner';\nimport { DevProxy } from './dev/proxy/proxy';\nimport { syncSchema } from './dev/api';\nimport {\n detectAppConfig,\n getWebInterfaceConfig,\n readTableSources,\n} from './dev/config/app-config';\nimport { initRequestLog, closeRequestLog } from './dev/logging/request-log';\nimport { initBrowserLog, closeBrowserLog } from './dev/logging/browser-log';\nimport { subscribeDevEvents } from './dev/ipc/session-events';\nimport { setupStdinCommands, type SessionState } from './dev/stdin-commands';\nimport { emitEvent } from './dev/ipc/ipc';\nimport {\n getApiKey,\n getApiBaseUrl,\n getUserId,\n getEnvironment,\n getConfigPath,\n} from './config';\nimport { initLoggerHeadless, log, type LogLevel } from './dev/logging/logger';\nimport { stablePort, detectGitBranch } from './dev/utils';\nimport { watchTableFiles } from './dev/config/table-watcher';\nimport { watchConfigFile } from './dev/config/config-watcher';\n\n/**\n * Options for headless dev mode.\n */\nexport interface HeadlessOptions {\n /** Working directory containing mindstudio.json. Defaults to process.cwd(). */\n cwd?: string;\n /** Port the dev server is running on. If omitted, reads from web.json. If neither, proxy is skipped. */\n devPort?: number;\n /** Preferred port for the local proxy. Defaults to a stable port derived from the app ID. */\n proxyPort?: number;\n /** Bind address for the proxy server. Use '0.0.0.0' for hosted sandboxes. Defaults to '127.0.0.1'. */\n bindAddress?: string;\n /** Log level for stderr output. Defaults to 'info'. */\n logLevel?: LogLevel;\n /** URL for the browser agent script. Defaults to unpkg latest. Set to an ngrok URL for development. */\n browserAgentUrl?: string;\n}\n\n\n// ---------------------------------------------------------------------------\n// Session lifecycle\n// ---------------------------------------------------------------------------\n\nasync function startSession(\n cwd: string,\n opts: HeadlessOptions,\n state: SessionState,\n shutdown: () => Promise<void>,\n): Promise<boolean> {\n const bindAddress = opts.bindAddress ?? '127.0.0.1';\n\n // Read fresh config\n const appConfig = detectAppConfig(cwd);\n if (!appConfig) {\n emitEvent('config-error', { message: 'No valid mindstudio.json found in ' + cwd });\n return false;\n }\n\n if (!appConfig.appId) {\n emitEvent('config-error', { message: 'Missing \"appId\" in mindstudio.json' });\n return false;\n }\n\n state.appConfig = appConfig;\n\n // Resolve dev port\n let devPort = opts.devPort ?? null;\n if (devPort === null) {\n const webConfig = getWebInterfaceConfig(appConfig, cwd);\n devPort = webConfig?.devPort ?? null;\n }\n\n emitEvent('session-starting', { appId: appConfig.appId, name: appConfig.name });\n\n try {\n // Start platform session\n const branch = detectGitBranch();\n const runner = new DevRunner(appConfig.appId, cwd, {\n branch,\n methods: appConfig.methods.map((m) => ({ id: m.id, export: m.export, path: m.path })),\n });\n runner.setAppConfig(appConfig);\n const session = await runner.start();\n state.runner = runner;\n\n // Initialize logs\n initRequestLog(cwd);\n initBrowserLog(cwd);\n\n // Sync schema\n if (appConfig.tables.length > 0) {\n try {\n const tableSources = readTableSources(appConfig, cwd);\n if (tableSources.length > 0) {\n const syncResult = await syncSchema(appConfig.appId, session.sessionId, tableSources);\n session.databases = syncResult.databases;\n emitEvent('schema-sync-completed', {\n created: syncResult.created,\n altered: syncResult.altered,\n errors: syncResult.errors,\n });\n } else {\n log.warn('session', 'No table source files found, skipping schema sync', {\n expected: appConfig.tables.map((t) => t.path),\n });\n }\n } catch (err) {\n emitEvent('schema-sync-completed', {\n created: [],\n altered: [],\n errors: [err instanceof Error ? err.message : 'Schema sync failed'],\n });\n }\n }\n\n // Start or reuse proxy\n if (devPort !== null && session.clientContext) {\n if (state.proxy) {\n // Proxy persists across restarts — just update the context\n state.proxy.updateClientContext(session.clientContext);\n } else {\n const proxy = new DevProxy(devPort, session.clientContext, bindAddress, opts.browserAgentUrl);\n const preferred = opts.proxyPort ?? stablePort(appConfig.appId);\n const proxyPort = await proxy.start(preferred);\n state.proxy = proxy;\n state.proxyPort = proxyPort;\n }\n\n runner.setProxyUrl(`http://${bindAddress === '0.0.0.0' ? 'localhost' : bindAddress}:${state.proxyPort}`);\n runner.setProxy(state.proxy);\n }\n\n emitEvent('session-started', {\n sessionId: session.sessionId,\n releaseId: session.releaseId,\n branch: session.branch,\n proxyPort: state.proxyPort,\n proxyUrl: state.proxyPort\n ? `http://${bindAddress === '0.0.0.0' ? 'localhost' : bindAddress}:${state.proxyPort}/`\n : null,\n webInterfaceUrl: session.webInterfaceUrl,\n roles: appConfig.roles.map((r) => ({ id: r.id, name: r.name ?? r.id, description: r.description })),\n scenarios: appConfig.scenarios.map((s) => ({\n id: s.id,\n name: s.name ?? s.export,\n description: s.description,\n path: s.path,\n roles: s.roles,\n })),\n });\n\n // Subscribe to runner events\n state.unsubscribers.push(...subscribeDevEvents(shutdown));\n\n // Watch table source files for changes — auto-sync without session restart\n setupTableWatchers(cwd, state);\n\n // Start polling for platform method requests now that schema sync,\n // proxy, and watchers are all set up. Starting earlier would risk\n // executing methods against stale session state (e.g. missing tables).\n runner.startPolling();\n\n return true;\n } catch (err) {\n emitEvent('config-error', {\n message: err instanceof Error ? err.message : 'Failed to start session',\n });\n return false;\n }\n}\n\nfunction setupTableWatchers(cwd: string, state: SessionState): void {\n if (!state.appConfig || state.appConfig.tables.length === 0) return;\n\n const cleanup = watchTableFiles(state.appConfig.tables, cwd, async () => {\n if (!state.runner || !state.appConfig?.appId) return;\n const session = state.runner.getSession();\n if (!session) return;\n\n emitEvent('schema-sync-started');\n log.info('session', 'Table source file changed, syncing schema');\n\n try {\n const tableSources = readTableSources(state.appConfig, cwd);\n if (tableSources.length > 0) {\n const result = await syncSchema(state.appConfig.appId, session.sessionId, tableSources);\n session.databases = result.databases;\n emitEvent('schema-sync-completed', {\n created: result.created,\n altered: result.altered,\n errors: result.errors,\n });\n log.info('session', 'Schema sync complete', { created: result.created, altered: result.altered });\n } else {\n log.warn('session', 'Table source file change detected but file(s) still missing', {\n expected: state.appConfig.tables.map((t) => t.path),\n });\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : 'Schema sync failed';\n emitEvent('schema-sync-completed', { created: [], altered: [], errors: [message] });\n log.warn('session', 'Schema sync failed', { error: message });\n }\n });\n\n state.unsubscribers.push(cleanup);\n}\n\n/** Tear down the runner, logs, and watchers. Proxy stays alive for reuse. */\nasync function teardownRunner(state: SessionState): Promise<void> {\n for (const unsub of state.unsubscribers) unsub();\n state.unsubscribers = [];\n\n if (state.runner) {\n await state.runner.stop().catch(() => {});\n state.runner = null;\n }\n\n closeRequestLog();\n closeBrowserLog();\n}\n\n/** Full teardown including proxy. Used on process shutdown. */\nasync function teardownAll(state: SessionState): Promise<void> {\n await teardownRunner(state);\n\n state.proxy?.stop();\n state.proxy = null;\n state.proxyPort = null;\n}\n\n// ---------------------------------------------------------------------------\n// Entry point\n// ---------------------------------------------------------------------------\n\n/**\n * Start the dev tunnel in headless mode.\n */\nexport async function startHeadless(opts: HeadlessOptions = {}): Promise<void> {\n initLoggerHeadless(opts.logLevel ?? 'info');\n\n const cwd = opts.cwd ?? process.cwd();\n\n const apiKey = getApiKey();\n const userId = getUserId();\n log.info('session', 'Startup config', {\n configPath: getConfigPath(),\n environment: getEnvironment(),\n apiBaseUrl: getApiBaseUrl(),\n hasApiKey: !!apiKey,\n apiKeyPrefix: apiKey ? apiKey.slice(0, 8) + '...' : null,\n hasUserId: !!userId,\n userId: userId ?? null,\n cwd,\n });\n\n const state: SessionState = {\n runner: null,\n proxy: null,\n appConfig: null,\n proxyPort: null,\n unsubscribers: [],\n };\n\n let restarting = false;\n let cleanupConfigWatcher: (() => void) | undefined;\n\n let stopping = false;\n const shutdown = async () => {\n if (stopping) return;\n stopping = true;\n emitEvent('session-stopping');\n cleanupConfigWatcher?.();\n await teardownAll(state);\n emitEvent('session-stopped');\n };\n\n process.on('SIGTERM', () => { shutdown().then(() => process.exit(0)); });\n process.on('SIGINT', () => { shutdown().then(() => process.exit(0)); });\n\n // Initial session start — crash if it fails so the process manager can retry\n const ok = await startSession(cwd, opts, state, shutdown);\n if (!ok) {\n process.exit(1);\n }\n\n // Stdin command loop\n setupStdinCommands(state, cwd);\n\n // Watch mindstudio.json for changes\n cleanupConfigWatcher = watchConfigFile(cwd, async () => {\n if (stopping || restarting) {\n log.debug('session', 'Config change ignored (restart in progress)');\n return;\n }\n restarting = true;\n try {\n log.info('session', 'mindstudio.json changed, restarting dev session');\n emitEvent('config-changed');\n await teardownRunner(state);\n const ok = await startSession(cwd, opts, state, shutdown);\n if (ok && state.proxy) {\n // Proxy stayed alive — clients are still connected, reload them\n state.proxy.broadcastToClients('reload');\n }\n } finally {\n restarting = false;\n }\n });\n\n // Keep the process alive — the poll loop runs in DevRunner\n await new Promise<void>(() => {});\n}\n"],"mappings":"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","names":["ok"]}
|
|
@@ -977,6 +977,63 @@ function formatErrorForDisplay(error) {
|
|
|
977
977
|
return parts.join("\n");
|
|
978
978
|
}
|
|
979
979
|
|
|
980
|
+
// src/dev/execution/agent-config.ts
|
|
981
|
+
import { readFileSync } from "fs";
|
|
982
|
+
import { join as join5, dirname as dirname2 } from "path";
|
|
983
|
+
function readAgentConfig(projectRoot, appConfig) {
|
|
984
|
+
const agentInterface = appConfig.interfaces.find(
|
|
985
|
+
(i) => i.type === "agent" && i.enabled !== false
|
|
986
|
+
);
|
|
987
|
+
if (!agentInterface) {
|
|
988
|
+
throw new Error("No agent interface configured in mindstudio.json");
|
|
989
|
+
}
|
|
990
|
+
const configPath = join5(projectRoot, agentInterface.path);
|
|
991
|
+
let raw;
|
|
992
|
+
try {
|
|
993
|
+
raw = readFileSync(configPath, "utf-8");
|
|
994
|
+
} catch {
|
|
995
|
+
throw new Error(
|
|
996
|
+
`Agent config not found at ${agentInterface.path} \u2014 run your build command`
|
|
997
|
+
);
|
|
998
|
+
}
|
|
999
|
+
const parsed = JSON.parse(raw);
|
|
1000
|
+
const config2 = parsed.agent ?? parsed;
|
|
1001
|
+
const agentDir = dirname2(configPath);
|
|
1002
|
+
const systemPromptPath = join5(agentDir, config2.systemPrompt);
|
|
1003
|
+
let systemPrompt;
|
|
1004
|
+
try {
|
|
1005
|
+
systemPrompt = readFileSync(systemPromptPath, "utf-8");
|
|
1006
|
+
} catch {
|
|
1007
|
+
throw new Error(
|
|
1008
|
+
`Agent system prompt not found at ${config2.systemPrompt} \u2014 run your build command`
|
|
1009
|
+
);
|
|
1010
|
+
}
|
|
1011
|
+
const tools = (config2.tools ?? []).map(
|
|
1012
|
+
(tool) => {
|
|
1013
|
+
const descPath = join5(agentDir, tool.description);
|
|
1014
|
+
let description;
|
|
1015
|
+
try {
|
|
1016
|
+
description = readFileSync(descPath, "utf-8");
|
|
1017
|
+
} catch {
|
|
1018
|
+
throw new Error(
|
|
1019
|
+
`Agent tool description not found at ${tool.description} for method "${tool.method}" \u2014 run your build command`
|
|
1020
|
+
);
|
|
1021
|
+
}
|
|
1022
|
+
return {
|
|
1023
|
+
name: tool.method,
|
|
1024
|
+
description
|
|
1025
|
+
};
|
|
1026
|
+
}
|
|
1027
|
+
);
|
|
1028
|
+
return {
|
|
1029
|
+
model: config2.model,
|
|
1030
|
+
temperature: config2.temperature,
|
|
1031
|
+
maxTokens: config2.maxTokens,
|
|
1032
|
+
systemPrompt,
|
|
1033
|
+
tools
|
|
1034
|
+
};
|
|
1035
|
+
}
|
|
1036
|
+
|
|
980
1037
|
// src/dev/execution/runner.ts
|
|
981
1038
|
var DevRunner = class {
|
|
982
1039
|
constructor(appId, projectRoot, startOpts = {}) {
|
|
@@ -991,6 +1048,7 @@ var DevRunner = class {
|
|
|
991
1048
|
hadConnectionWarning = false;
|
|
992
1049
|
proxyUrl;
|
|
993
1050
|
proxy = null;
|
|
1051
|
+
appConfig = null;
|
|
994
1052
|
roleOverride = null;
|
|
995
1053
|
// proxyUrl is sent on every poll request so the platform dashboard can
|
|
996
1054
|
// show the developer's preview URL. Also included in the start request
|
|
@@ -1002,6 +1060,9 @@ var DevRunner = class {
|
|
|
1002
1060
|
setProxy(proxy) {
|
|
1003
1061
|
this.proxy = proxy;
|
|
1004
1062
|
}
|
|
1063
|
+
setAppConfig(appConfig) {
|
|
1064
|
+
this.appConfig = appConfig;
|
|
1065
|
+
}
|
|
1005
1066
|
async start() {
|
|
1006
1067
|
if (this.isRunning) {
|
|
1007
1068
|
throw new Error("DevRunner is already running");
|
|
@@ -1263,6 +1324,10 @@ var DevRunner = class {
|
|
|
1263
1324
|
}
|
|
1264
1325
|
}
|
|
1265
1326
|
async handleRequest(request) {
|
|
1327
|
+
if (request.type === "get-agent-config") {
|
|
1328
|
+
await this.handleGetAgentConfig(request);
|
|
1329
|
+
return;
|
|
1330
|
+
}
|
|
1266
1331
|
const startTime = Date.now();
|
|
1267
1332
|
devRequestEvents.emitStart({
|
|
1268
1333
|
id: request.requestId,
|
|
@@ -1373,6 +1438,44 @@ var DevRunner = class {
|
|
|
1373
1438
|
});
|
|
1374
1439
|
}
|
|
1375
1440
|
}
|
|
1441
|
+
async handleGetAgentConfig(request) {
|
|
1442
|
+
const startTime = Date.now();
|
|
1443
|
+
log.info("runner", "Agent config requested", { requestId: request.requestId, sessionId: this.session.sessionId });
|
|
1444
|
+
try {
|
|
1445
|
+
if (!this.appConfig) {
|
|
1446
|
+
throw new Error("App config not available");
|
|
1447
|
+
}
|
|
1448
|
+
const bundle = readAgentConfig(this.projectRoot, this.appConfig);
|
|
1449
|
+
await submitDevResult(
|
|
1450
|
+
this.appId,
|
|
1451
|
+
this.session.sessionId,
|
|
1452
|
+
request.requestId,
|
|
1453
|
+
{
|
|
1454
|
+
type: "get-agent-config",
|
|
1455
|
+
success: true,
|
|
1456
|
+
output: bundle
|
|
1457
|
+
}
|
|
1458
|
+
);
|
|
1459
|
+
log.info("runner", "Agent config sent", { requestId: request.requestId, duration: Date.now() - startTime });
|
|
1460
|
+
} catch (err) {
|
|
1461
|
+
const message = err instanceof Error ? err.message : "Unknown error";
|
|
1462
|
+
log.error("runner", "Agent config failed", { requestId: request.requestId, error: message });
|
|
1463
|
+
try {
|
|
1464
|
+
await submitDevResult(
|
|
1465
|
+
this.appId,
|
|
1466
|
+
this.session.sessionId,
|
|
1467
|
+
request.requestId,
|
|
1468
|
+
{
|
|
1469
|
+
type: "get-agent-config",
|
|
1470
|
+
success: false,
|
|
1471
|
+
error: { message }
|
|
1472
|
+
}
|
|
1473
|
+
);
|
|
1474
|
+
} catch (submitErr) {
|
|
1475
|
+
log.error("runner", "Failed to report agent config error to platform", { error: submitErr instanceof Error ? submitErr.message : String(submitErr) });
|
|
1476
|
+
}
|
|
1477
|
+
}
|
|
1478
|
+
}
|
|
1376
1479
|
/**
|
|
1377
1480
|
* Attempt to refresh expired auth credentials via the device auth flow.
|
|
1378
1481
|
* Opens the browser for the user to re-authorize, polls for the new token.
|
|
@@ -1492,11 +1595,16 @@ var ClientRegistry = class {
|
|
|
1492
1595
|
let fallback = null;
|
|
1493
1596
|
for (const client of this.clients.values()) {
|
|
1494
1597
|
if (client.activeCommandId) continue;
|
|
1598
|
+
if (client.mode === "mirror") continue;
|
|
1495
1599
|
if (client.mode === "iframe") return client;
|
|
1496
1600
|
if (!fallback) fallback = client;
|
|
1497
1601
|
}
|
|
1498
1602
|
return fallback;
|
|
1499
1603
|
}
|
|
1604
|
+
/** Get all connected mirror-mode clients (for relaying mirror events). */
|
|
1605
|
+
getMirrorClients() {
|
|
1606
|
+
return [...this.clients.values()].filter((c) => c.mode === "mirror");
|
|
1607
|
+
}
|
|
1500
1608
|
getAll() {
|
|
1501
1609
|
return [...this.clients.values()];
|
|
1502
1610
|
}
|
|
@@ -1777,7 +1885,7 @@ var DevProxy = class _DevProxy {
|
|
|
1777
1885
|
return;
|
|
1778
1886
|
}
|
|
1779
1887
|
clearTimeout(helloTimeout);
|
|
1780
|
-
const mode = msg.mode === "iframe" ? "iframe" : "standalone";
|
|
1888
|
+
const mode = msg.mode === "iframe" ? "iframe" : msg.mode === "mirror" ? "mirror" : "standalone";
|
|
1781
1889
|
const viewport = msg.viewport || {
|
|
1782
1890
|
w: 0,
|
|
1783
1891
|
h: 0
|
|
@@ -1799,6 +1907,9 @@ var DevProxy = class _DevProxy {
|
|
|
1799
1907
|
appendBrowserLogEntries(msg.entries);
|
|
1800
1908
|
}
|
|
1801
1909
|
break;
|
|
1910
|
+
case "mirror":
|
|
1911
|
+
this.relayMirrorEvents(data.toString());
|
|
1912
|
+
break;
|
|
1802
1913
|
}
|
|
1803
1914
|
});
|
|
1804
1915
|
ws.on("pong", () => {
|
|
@@ -1950,6 +2061,10 @@ var DevProxy = class _DevProxy {
|
|
|
1950
2061
|
this.handleFontProxy(clientReq, clientRes);
|
|
1951
2062
|
return;
|
|
1952
2063
|
}
|
|
2064
|
+
if (clientReq.url === "/__mindstudio_dev__/mirror" && clientReq.method === "GET") {
|
|
2065
|
+
this.serveMirrorPage(clientRes);
|
|
2066
|
+
return;
|
|
2067
|
+
}
|
|
1953
2068
|
}
|
|
1954
2069
|
if (clientReq.method === "OPTIONS" && clientReq.headers.origin) {
|
|
1955
2070
|
clientRes.writeHead(204, {
|
|
@@ -2040,6 +2155,91 @@ var DevProxy = class _DevProxy {
|
|
|
2040
2155
|
clientRes.end();
|
|
2041
2156
|
});
|
|
2042
2157
|
}
|
|
2158
|
+
/** Relay a raw mirror message (already JSON-stringified) to all mirror viewers. */
|
|
2159
|
+
relayMirrorEvents(raw) {
|
|
2160
|
+
const mirrors = this.clients.getMirrorClients();
|
|
2161
|
+
for (const client of mirrors) {
|
|
2162
|
+
try {
|
|
2163
|
+
client.ws.send(raw);
|
|
2164
|
+
} catch {
|
|
2165
|
+
}
|
|
2166
|
+
}
|
|
2167
|
+
}
|
|
2168
|
+
/** Serve the mirror replay page — an rrweb Replayer in live mode. */
|
|
2169
|
+
serveMirrorPage(res) {
|
|
2170
|
+
const html = `<!DOCTYPE html>
|
|
2171
|
+
<html>
|
|
2172
|
+
<head>
|
|
2173
|
+
<meta charset="utf-8">
|
|
2174
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
2175
|
+
<title>Mobile Mirror</title>
|
|
2176
|
+
<style>
|
|
2177
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
2178
|
+
html, body { height: 100%; background: #111; overflow: hidden; }
|
|
2179
|
+
#player { width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; }
|
|
2180
|
+
.replayer-wrapper { box-shadow: 0 0 40px rgba(0,0,0,0.5); border-radius: 4px; overflow: hidden; }
|
|
2181
|
+
#status { position: fixed; bottom: 16px; left: 50%; transform: translateX(-50%); color: #666; font-family: -apple-system, system-ui, sans-serif; font-size: 13px; }
|
|
2182
|
+
</style>
|
|
2183
|
+
</head>
|
|
2184
|
+
<body>
|
|
2185
|
+
<div id="player"></div>
|
|
2186
|
+
<div id="status">Waiting for mobile device...</div>
|
|
2187
|
+
<script src="https://cdn.jsdelivr.net/npm/rrweb@2.0.0-alpha.13/dist/rrweb.umd.cjs.js"></script>
|
|
2188
|
+
<script>
|
|
2189
|
+
(function() {
|
|
2190
|
+
var proto = location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
2191
|
+
var ws = new WebSocket(proto + '//' + location.host + '/__mindstudio_dev__/ws');
|
|
2192
|
+
var replayer = null;
|
|
2193
|
+
var status = document.getElementById('status');
|
|
2194
|
+
|
|
2195
|
+
ws.onopen = function() {
|
|
2196
|
+
ws.send(JSON.stringify({
|
|
2197
|
+
type: 'hello',
|
|
2198
|
+
mode: 'mirror',
|
|
2199
|
+
url: location.href,
|
|
2200
|
+
viewport: { w: window.innerWidth, h: window.innerHeight }
|
|
2201
|
+
}));
|
|
2202
|
+
};
|
|
2203
|
+
|
|
2204
|
+
ws.onmessage = function(e) {
|
|
2205
|
+
var msg;
|
|
2206
|
+
try { msg = JSON.parse(e.data); } catch(e) { return; }
|
|
2207
|
+
|
|
2208
|
+
if (msg.type !== 'mirror' || !Array.isArray(msg.events)) return;
|
|
2209
|
+
|
|
2210
|
+
for (var i = 0; i < msg.events.length; i++) {
|
|
2211
|
+
var event = msg.events[i];
|
|
2212
|
+
if (!replayer) {
|
|
2213
|
+
replayer = new rrweb.Replayer([], {
|
|
2214
|
+
root: document.getElementById('player'),
|
|
2215
|
+
liveMode: true,
|
|
2216
|
+
insertStyleRules: [
|
|
2217
|
+
'.replayer-wrapper { position: relative !important; }',
|
|
2218
|
+
],
|
|
2219
|
+
});
|
|
2220
|
+
replayer.startLive(Date.now() - 500);
|
|
2221
|
+
status.textContent = 'Connected';
|
|
2222
|
+
setTimeout(function() { status.style.opacity = '0'; }, 2000);
|
|
2223
|
+
}
|
|
2224
|
+
replayer.addEvent(event);
|
|
2225
|
+
}
|
|
2226
|
+
};
|
|
2227
|
+
|
|
2228
|
+
ws.onclose = function() {
|
|
2229
|
+
status.style.opacity = '1';
|
|
2230
|
+
status.textContent = 'Disconnected \u2014 reconnecting...';
|
|
2231
|
+
setTimeout(function() { location.reload(); }, 2000);
|
|
2232
|
+
};
|
|
2233
|
+
})();
|
|
2234
|
+
</script>
|
|
2235
|
+
</body>
|
|
2236
|
+
</html>`;
|
|
2237
|
+
res.writeHead(200, {
|
|
2238
|
+
"content-type": "text/html; charset=utf-8",
|
|
2239
|
+
"cache-control": "no-store"
|
|
2240
|
+
});
|
|
2241
|
+
res.end(html);
|
|
2242
|
+
}
|
|
2043
2243
|
/**
|
|
2044
2244
|
* Proxy a cross-origin font stylesheet or font file through our server,
|
|
2045
2245
|
* adding CORS headers so the browser agent can read the @font-face rules.
|
|
@@ -2143,13 +2343,13 @@ ${agentScript}`;
|
|
|
2143
2343
|
};
|
|
2144
2344
|
|
|
2145
2345
|
// src/dev/config/app-config.ts
|
|
2146
|
-
import { readFileSync, existsSync as existsSync2 } from "fs";
|
|
2147
|
-
import { join as
|
|
2346
|
+
import { readFileSync as readFileSync2, existsSync as existsSync2 } from "fs";
|
|
2347
|
+
import { join as join6, dirname as dirname3 } from "path";
|
|
2148
2348
|
function detectAppConfig(cwd = process.cwd()) {
|
|
2149
|
-
const appJsonPath =
|
|
2349
|
+
const appJsonPath = join6(cwd, "mindstudio.json");
|
|
2150
2350
|
if (!existsSync2(appJsonPath)) return null;
|
|
2151
2351
|
try {
|
|
2152
|
-
const raw =
|
|
2352
|
+
const raw = readFileSync2(appJsonPath, "utf-8");
|
|
2153
2353
|
const parsed = JSON.parse(raw);
|
|
2154
2354
|
if (!parsed.name || !Array.isArray(parsed.methods)) {
|
|
2155
2355
|
return null;
|
|
@@ -2185,12 +2385,12 @@ function getWebInterfaceConfig(appConfig, cwd = process.cwd()) {
|
|
|
2185
2385
|
if (!webInterface) {
|
|
2186
2386
|
return null;
|
|
2187
2387
|
}
|
|
2188
|
-
const configPath =
|
|
2388
|
+
const configPath = join6(cwd, webInterface.path);
|
|
2189
2389
|
if (!existsSync2(configPath)) {
|
|
2190
2390
|
return null;
|
|
2191
2391
|
}
|
|
2192
2392
|
try {
|
|
2193
|
-
const raw =
|
|
2393
|
+
const raw = readFileSync2(configPath, "utf-8");
|
|
2194
2394
|
const parsed = JSON.parse(raw);
|
|
2195
2395
|
const web = parsed.web;
|
|
2196
2396
|
if (!web || typeof web !== "object") {
|
|
@@ -2211,18 +2411,18 @@ function getWebProjectDir(appConfig, cwd = process.cwd()) {
|
|
|
2211
2411
|
if (!webInterface) {
|
|
2212
2412
|
return null;
|
|
2213
2413
|
}
|
|
2214
|
-
return
|
|
2414
|
+
return dirname3(join6(cwd, webInterface.path));
|
|
2215
2415
|
}
|
|
2216
2416
|
function readTableSources(appConfig, cwd = process.cwd()) {
|
|
2217
2417
|
const results = [];
|
|
2218
2418
|
for (const table of appConfig.tables) {
|
|
2219
|
-
const filePath =
|
|
2419
|
+
const filePath = join6(cwd, table.path);
|
|
2220
2420
|
if (!existsSync2(filePath)) {
|
|
2221
2421
|
log.warn("config", "Table source file not found", { table: table.export, path: table.path });
|
|
2222
2422
|
continue;
|
|
2223
2423
|
}
|
|
2224
2424
|
try {
|
|
2225
|
-
const source =
|
|
2425
|
+
const source = readFileSync2(filePath, "utf-8");
|
|
2226
2426
|
const name = table.export;
|
|
2227
2427
|
results.push({ name, source });
|
|
2228
2428
|
} catch (err) {
|
|
@@ -2240,9 +2440,9 @@ function findDirsNeedingInstall(appConfig, cwd = process.cwd()) {
|
|
|
2240
2440
|
const firstMethodPath = appConfig.methods[0].path;
|
|
2241
2441
|
const parts = firstMethodPath.split("/");
|
|
2242
2442
|
for (let i = parts.length - 1; i >= 1; i--) {
|
|
2243
|
-
const candidate =
|
|
2244
|
-
if (existsSync2(
|
|
2245
|
-
if (!existsSync2(
|
|
2443
|
+
const candidate = join6(cwd, ...parts.slice(0, i));
|
|
2444
|
+
if (existsSync2(join6(candidate, "package.json"))) {
|
|
2445
|
+
if (!existsSync2(join6(candidate, "node_modules"))) {
|
|
2246
2446
|
dirs.push(candidate);
|
|
2247
2447
|
}
|
|
2248
2448
|
break;
|
|
@@ -2250,8 +2450,8 @@ function findDirsNeedingInstall(appConfig, cwd = process.cwd()) {
|
|
|
2250
2450
|
}
|
|
2251
2451
|
}
|
|
2252
2452
|
const webProjectDir = getWebProjectDir(appConfig, cwd);
|
|
2253
|
-
if (webProjectDir && existsSync2(
|
|
2254
|
-
if (!existsSync2(
|
|
2453
|
+
if (webProjectDir && existsSync2(join6(webProjectDir, "package.json"))) {
|
|
2454
|
+
if (!existsSync2(join6(webProjectDir, "node_modules"))) {
|
|
2255
2455
|
dirs.push(webProjectDir);
|
|
2256
2456
|
}
|
|
2257
2457
|
}
|
|
@@ -2280,11 +2480,11 @@ function detectGitBranch() {
|
|
|
2280
2480
|
|
|
2281
2481
|
// src/dev/config/table-watcher.ts
|
|
2282
2482
|
import { watch } from "chokidar";
|
|
2283
|
-
import { join as
|
|
2483
|
+
import { join as join7, dirname as dirname4, basename as basename2 } from "path";
|
|
2284
2484
|
function watchTableFiles(tables, cwd, onChanged) {
|
|
2285
2485
|
if (tables.length === 0) return () => {
|
|
2286
2486
|
};
|
|
2287
|
-
const filePaths = tables.map((t) =>
|
|
2487
|
+
const filePaths = tables.map((t) => join7(cwd, t.path));
|
|
2288
2488
|
let syncTimer;
|
|
2289
2489
|
const watcher = watch(filePaths, {
|
|
2290
2490
|
ignoreInitial: true,
|
|
@@ -2297,8 +2497,8 @@ function watchTableFiles(tables, cwd, onChanged) {
|
|
|
2297
2497
|
});
|
|
2298
2498
|
const dirToFiles = /* @__PURE__ */ new Map();
|
|
2299
2499
|
for (const table of tables) {
|
|
2300
|
-
const absPath =
|
|
2301
|
-
const dir =
|
|
2500
|
+
const absPath = join7(cwd, table.path);
|
|
2501
|
+
const dir = dirname4(absPath);
|
|
2302
2502
|
const file = basename2(absPath);
|
|
2303
2503
|
if (!dirToFiles.has(dir)) dirToFiles.set(dir, /* @__PURE__ */ new Set());
|
|
2304
2504
|
dirToFiles.get(dir).add(file);
|
|
@@ -2315,9 +2515,9 @@ function watchTableFiles(tables, cwd, onChanged) {
|
|
|
2315
2515
|
|
|
2316
2516
|
// src/dev/config/config-watcher.ts
|
|
2317
2517
|
import { watch as watch2 } from "chokidar";
|
|
2318
|
-
import { join as
|
|
2518
|
+
import { join as join8 } from "path";
|
|
2319
2519
|
function watchConfigFile(cwd, onChanged) {
|
|
2320
|
-
const configPath =
|
|
2520
|
+
const configPath = join8(cwd, "mindstudio.json");
|
|
2321
2521
|
let debounceTimer;
|
|
2322
2522
|
const watcher = watch2(configPath, {
|
|
2323
2523
|
ignoreInitial: true,
|
|
@@ -2384,4 +2584,4 @@ export {
|
|
|
2384
2584
|
watchTableFiles,
|
|
2385
2585
|
watchConfigFile
|
|
2386
2586
|
};
|
|
2387
|
-
//# sourceMappingURL=chunk-
|
|
2587
|
+
//# sourceMappingURL=chunk-WECAPRKN.js.map
|