feat: 增加 L1 native Web 单命令验收
This commit is contained in:
@@ -639,7 +639,9 @@ export function runManagedNodeWebProbeScript(
|
||||
result: reportResult,
|
||||
requestedStdoutType: "web-probe script report JSON",
|
||||
acceptParsed: isWebProbeScriptReportContract,
|
||||
forceArtifactFallbackReason: commandTimedOut ? "remote-command-timeout" : null,
|
||||
forceArtifactFallbackReason: commandTimedOut
|
||||
? "remote-command-timeout"
|
||||
: asyncJob !== null ? "remote-artifact-job-report" : null,
|
||||
artifactFallback: {
|
||||
path: runPaths.reportPath,
|
||||
nextCommand: runPaths.reportPath === null ? null : `trans ${options.node}:${spec.workspace} cat ${shellQuote(runPaths.reportPath)}`,
|
||||
|
||||
@@ -16,6 +16,7 @@ import { CliInputError } from "../output";
|
||||
import { HWLAB_NODE_CONTROL_PLANE_CONFIG_PATH, hwlabNodeControlPlaneCiGitWorkspaceSecret, hwlabNodeControlPlaneInfraHelp, runHwlabNodeControlPlaneInfra } from "../hwlab-node-control-plane";
|
||||
import { hwlabRuntimeLaneConfigPath, hwlabRuntimeLaneIds, hwlabRuntimeLaneSpec, hwlabRuntimeLaneSpecForNode, hwlabRuntimeNodeIds, isHwlabRuntimeLane, type HwlabRuntimeLane, type HwlabRuntimeLaneSpec, type HwlabRuntimeObservabilityRecordingRuleSpec, type HwlabRuntimeObservabilitySpec, type HwlabRuntimeObservabilityWarningAlertSpec, type HwlabRuntimePublicExposureSpec, type HwlabRuntimeWebProbeAlertThresholdsSpec, type HwlabRuntimeWebProbeProjectManagementSpec } from "../hwlab-node-lanes";
|
||||
import { nodeWebProbeConsoleVerifyScript } from "../hwlab-node-web-probe-console-verify";
|
||||
import { nodeWebProbeNativeReadinessScript } from "../hwlab-node-web-probe-native-readiness";
|
||||
import { nodeWebProbeScriptRunnerSource } from "../hwlab-node-web-probe-runner-source";
|
||||
import { nodeWebObserveAnalyzerSource } from "../hwlab-node-web-observe-analyzer-source";
|
||||
import { nodeWebObserveRunnerSource } from "../hwlab-node-web-observe-runner-source";
|
||||
@@ -2087,7 +2088,7 @@ export function rewriteDelegatedNodeString(value: string, scoped: ReturnType<typ
|
||||
|
||||
export function parseNodeWebProbeOptions(args: string[]): NodeWebProbeOptions {
|
||||
const [actionRaw] = args;
|
||||
if (actionRaw !== "run" && actionRaw !== "script" && actionRaw !== "screenshot" && actionRaw !== "observe" && actionRaw !== "sentinel" && actionRaw !== "opencode-smoke" && actionRaw !== "console-verify") throw new Error("web-probe usage: run|script|screenshot|observe|sentinel|opencode-smoke|console-verify --node NODE --lane vNN [--origin internal|public]");
|
||||
if (actionRaw !== "run" && actionRaw !== "script" && actionRaw !== "screenshot" && actionRaw !== "observe" && actionRaw !== "sentinel" && actionRaw !== "opencode-smoke" && actionRaw !== "console-verify" && actionRaw !== "native-readiness") throw new Error("web-probe usage: run|script|screenshot|observe|sentinel|opencode-smoke|console-verify|native-readiness --node NODE --lane vNN [--origin internal|public]");
|
||||
if (actionRaw === "sentinel") return parseNodeWebProbeSentinelOptions(args.slice(1));
|
||||
if (actionRaw === "observe") {
|
||||
const normalized = normalizeNodeWebProbeObserveArgs(args.slice(1));
|
||||
@@ -2122,6 +2123,61 @@ export function parseNodeWebProbeOptions(args: string[]): NodeWebProbeOptions {
|
||||
const browserProxyMode = browserProxyModeRaw === undefined ? undefined : parseWebProbeBrowserProxyMode(browserProxyModeRaw);
|
||||
return resolveNodeWebProbeCliOrigin(spec, optionValue(args, "--origin"), optionValue(args, "--url"), browserProxyMode);
|
||||
};
|
||||
if (actionRaw === "native-readiness") {
|
||||
assertKnownOptions(args.slice(1), new Set([
|
||||
"--node",
|
||||
"--lane",
|
||||
"--profile",
|
||||
"--timeout-ms",
|
||||
"--command-timeout-seconds",
|
||||
]), new Set([]));
|
||||
const native = spec.nativeDevelopment?.workbench;
|
||||
if (native === undefined) throw new Error(`${hwlabRuntimeLaneConfigPath()} node=${node} lane=${lane} does not declare nativeDevelopment.workbench`);
|
||||
const profiles = spec.webProbe?.nativeReadinessProfiles;
|
||||
const profileName = optionValue(args, "--profile") ?? spec.webProbe?.defaultNativeReadinessProfile;
|
||||
if (profiles === undefined || profileName === undefined) {
|
||||
throw new Error(`${hwlabRuntimeLaneConfigPath()} node=${node} lane=${lane} does not declare a default native readiness profile`);
|
||||
}
|
||||
const profile = profiles[profileName];
|
||||
if (profile === undefined) {
|
||||
throw new Error(`web-probe native-readiness unknown --profile ${profileName}; configured profiles: ${Object.keys(profiles).sort().join(", ")}`);
|
||||
}
|
||||
const timeoutMs = positiveIntegerOption(args, "--timeout-ms", profile.navigationTimeoutMs, 120_000);
|
||||
const commandTimeoutSeconds = positiveIntegerOption(args, "--command-timeout-seconds", profile.commandTimeoutSeconds, 3600);
|
||||
const configRef = `${hwlabRuntimeLaneConfigPath()}#templates.*.webProbeWorkbench.nativeReadinessProfiles.${profileName}`;
|
||||
const scriptText = nodeWebProbeNativeReadinessScript(profileName, profile, configRef);
|
||||
const url = `${profile.scheme}://${native.publicHost}:${native.web.port}`;
|
||||
const selectedOrigin = resolveNodeWebProbeCliOrigin(spec, undefined, url, "direct");
|
||||
const commandLabel = `web-probe native-readiness --node ${node} --lane ${lane} --profile ${profileName}`;
|
||||
return {
|
||||
action: "script",
|
||||
node,
|
||||
lane,
|
||||
url: selectedOrigin.url,
|
||||
originName: selectedOrigin.originName,
|
||||
originMode: selectedOrigin.originMode,
|
||||
originConfigPath: `${hwlabRuntimeLaneConfigPath()}#lanes.${lane}.targets.${node}.nativeDevelopment.workbench`,
|
||||
timeoutMs,
|
||||
viewport: `${profile.viewport.width}x${profile.viewport.height}`,
|
||||
browserProxyMode: selectedOrigin.browserProxyMode,
|
||||
browserProxyModeSource: selectedOrigin.browserProxyModeSource,
|
||||
commandTimeoutSeconds,
|
||||
scriptText,
|
||||
commandLabel,
|
||||
suppressAdHocWarning: true,
|
||||
generatedHints: [
|
||||
"L1 native Web readiness is a repo-owned typed command; public IP, port, path, DOM, interaction and failure rules come from owning YAML.",
|
||||
"Pass requires mounted DOM, a completed interaction, no browser errors, no critical failed responses and a screenshot artifact.",
|
||||
],
|
||||
generatedPreferredCommands: { rerun: commandLabel },
|
||||
scriptSource: {
|
||||
kind: "generated",
|
||||
path: `builtin:native-readiness/${profileName}`,
|
||||
byteCount: Buffer.byteLength(scriptText),
|
||||
sha256: `sha256:${createHash("sha256").update(scriptText).digest("hex")}`,
|
||||
},
|
||||
};
|
||||
}
|
||||
if (actionRaw === "console-verify") {
|
||||
assertKnownOptions(args.slice(1), new Set([
|
||||
"--node",
|
||||
|
||||
Reference in New Issue
Block a user