feat: add deterministic daily ops SOP and verified dev lifecycle
This commit is contained in:
1 parent
dac4af53e0
commit
2aafb6f92f
16 files changed
+935
-5
No files matched your search
@@ -0,0 +1,60 @@
|
||||
/** Opt-in development readiness only. Never exposes a network/debug endpoint. */
|
||||
import { app, type BrowserWindow } from 'electron'
|
||||
import { promises as fs } from 'node:fs'
|
||||
import { join, resolve } from 'node:path'
|
||||
import { randomUUID } from 'node:crypto'
|
||||
|
||||
export function attachDevOpsHealth(window: BrowserWindow): void {
|
||||
const runId = process.env.YCODE_OPS_RUN_ID
|
||||
if (app.isPackaged || !runId || !/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(runId)) return
|
||||
const root = resolve(process.cwd()), directory = join(root, '.tmp', 'ops')
|
||||
const destination = join(directory, 'renderer-health.json')
|
||||
let busy = false, stopped = false, responsive = true, generation = 0
|
||||
let writes: Promise<void> = Promise.resolve()
|
||||
const invalidate = (): void => { generation++; void publish(false).catch(() => undefined) }
|
||||
function publish(ready: boolean): Promise<void> {
|
||||
const expectedGeneration = generation
|
||||
const operation = writes.then(async () => {
|
||||
if (expectedGeneration !== generation) return
|
||||
// The controller owns this directory. Do not create or follow an arbitrary linked path.
|
||||
const actualDirectory = await fs.realpath(directory)
|
||||
const canonical = (value: string): string => process.platform === 'win32' ? value.toLowerCase() : value
|
||||
if (canonical(actualDirectory) !== canonical(directory)) return
|
||||
const temporary = join(directory, `.health-${process.pid}-${randomUUID()}.tmp`)
|
||||
try {
|
||||
await fs.writeFile(temporary, JSON.stringify({ runId, pid: process.pid,
|
||||
rendererPid: window.webContents.isDestroyed() ? 0 : window.webContents.getOSProcessId(),
|
||||
checkedAt: Date.now(), ready }), { flag: 'wx', mode: 0o600 })
|
||||
if (expectedGeneration === generation) await fs.rename(temporary, destination)
|
||||
} finally { await fs.rm(temporary, { force: true }).catch(() => undefined) }
|
||||
})
|
||||
writes = operation.catch(() => undefined)
|
||||
return operation
|
||||
}
|
||||
async function probe(): Promise<void> {
|
||||
if (busy || stopped) return
|
||||
busy = true
|
||||
const current = generation
|
||||
try {
|
||||
let ready = false
|
||||
if (responsive && !window.isDestroyed() && !window.webContents.isDestroyed() && !window.webContents.isLoadingMainFrame()) {
|
||||
ready = await window.webContents.executeJavaScript(`(() => {
|
||||
const shell = document.querySelector('.app-shell');
|
||||
if (document.readyState !== 'complete' || !shell || !shell.querySelector('button')) return false;
|
||||
const rect = shell.getBoundingClientRect(), style = getComputedStyle(shell);
|
||||
return rect.width > 0 && rect.height > 0 && style.display !== 'none' && style.visibility !== 'hidden';
|
||||
})()`)
|
||||
}
|
||||
if (!stopped && current === generation) await publish(ready === true)
|
||||
} catch { if (!stopped && current === generation) await publish(false).catch(() => undefined) }
|
||||
finally { busy = false }
|
||||
}
|
||||
window.webContents.on('did-start-loading', invalidate)
|
||||
window.webContents.on('render-process-gone', invalidate)
|
||||
window.on('unresponsive', () => { responsive = false; invalidate() })
|
||||
window.on('responsive', () => { responsive = true; void probe() })
|
||||
const timer = setInterval(() => { void probe() }, 1000)
|
||||
timer.unref()
|
||||
window.on('closed', () => { stopped = true; clearInterval(timer); invalidate() })
|
||||
void probe()
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import { attachDevOpsHealth } from './dev-ops-health'
|
||||
/**
|
||||
* Electron 主进程入口。
|
||||
*
|
||||
@@ -109,6 +110,7 @@ function createWindow(): void {
|
||||
}
|
||||
})
|
||||
|
||||
attachDevOpsHealth(mainWindow)
|
||||
mainWindow.on('ready-to-show', () => mainWindow?.show())
|
||||
mainWindow.on('closed', () => {
|
||||
mainWindow = null
|
||||
|
||||
Reference in new issue
Block a user