mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-21 00:38:14 +08:00
feat(ql3): establish 3.0 incubation baseline
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
import type { ExecutionContext } from '../domain/execution';
|
||||
import { normalizeExecutionContext } from '../domain/executionContext';
|
||||
import { assertLocalExecutionArtifactId } from '../domain/localExecutionArtifact';
|
||||
import {
|
||||
assertLocalExecutionContextRef,
|
||||
normalizeLocalExecutionContextRecipe,
|
||||
} from '../domain/localExecutionContextRecipe';
|
||||
import { assertRunDispatchCandidate } from '../domain/runDispatchCandidate';
|
||||
import type { LocalExecutionArtifactAllocator } from '../ports/localExecutionArtifactAllocator';
|
||||
import type { LocalExecutionContextMaterializer } from '../ports/localExecutionContextMaterializer';
|
||||
import type { LocalExecutionContextRecipeSource } from '../ports/localExecutionContextRecipeSource';
|
||||
import type { LocalSecretEnvironmentProvider } from '../ports/localSecretEnvironmentProvider';
|
||||
|
||||
const VALIDATION_OUTPUT = Object.freeze({ async write() {} });
|
||||
|
||||
/** Resolves public values, ephemeral Secrets and an Attempt-scoped Artifact. */
|
||||
export class RecipeLocalExecutionContextMaterializer
|
||||
implements LocalExecutionContextMaterializer
|
||||
{
|
||||
constructor(
|
||||
private readonly recipes: LocalExecutionContextRecipeSource,
|
||||
private readonly artifacts: LocalExecutionArtifactAllocator,
|
||||
private readonly secrets?: LocalSecretEnvironmentProvider,
|
||||
) {}
|
||||
|
||||
async prepare(
|
||||
request: Parameters<LocalExecutionContextMaterializer['prepare']>[0],
|
||||
): ReturnType<LocalExecutionContextMaterializer['prepare']> {
|
||||
if (!request || typeof request !== 'object' || Array.isArray(request)) {
|
||||
throw new TypeError('Local execution context request must be an object');
|
||||
}
|
||||
assertRunDispatchCandidate(request.candidate);
|
||||
assertLocalExecutionContextRef(request.contextRef);
|
||||
const recipe = await this.recipes.resolve(request.contextRef);
|
||||
if (!recipe) return null;
|
||||
const normalized = normalizeLocalExecutionContextRecipe(recipe);
|
||||
if (normalized.contextRef !== request.contextRef) {
|
||||
throw new TypeError('Local context recipe does not match contextRef');
|
||||
}
|
||||
|
||||
const secretRefs = [
|
||||
...new Set(
|
||||
normalized.environment.flatMap((binding) =>
|
||||
binding.kind === 'secret' ? [binding.secretRef] : [],
|
||||
),
|
||||
),
|
||||
];
|
||||
let secretValues: readonly string[] = [];
|
||||
if (secretRefs.length > 0) {
|
||||
if (!this.secrets) return null;
|
||||
const resolved = await this.secrets.resolve(
|
||||
Object.freeze({
|
||||
candidate: Object.freeze({ ...request.candidate }),
|
||||
secretRefs: Object.freeze([...secretRefs]),
|
||||
}),
|
||||
);
|
||||
if (!resolved) return null;
|
||||
if (resolved.length !== secretRefs.length) {
|
||||
throw new TypeError('Local Secret provider returned an invalid result');
|
||||
}
|
||||
secretValues = resolved;
|
||||
}
|
||||
const byRef = new Map(
|
||||
secretRefs.map((secretRef, index) => [secretRef, secretValues[index]]),
|
||||
);
|
||||
const environment: Record<string, string> = Object.create(null);
|
||||
for (const binding of normalized.environment) {
|
||||
environment[binding.name] =
|
||||
binding.kind === 'public'
|
||||
? binding.value
|
||||
: (byRef.get(binding.secretRef) as string);
|
||||
}
|
||||
const validatedEnvironment = normalizeExecutionContext({
|
||||
environment,
|
||||
output: VALIDATION_OUTPUT,
|
||||
}).environment;
|
||||
|
||||
const artifact = await this.artifacts.prepare(request.candidate);
|
||||
try {
|
||||
assertLocalExecutionArtifactId(artifact.logArtifactId);
|
||||
const context: ExecutionContext = normalizeExecutionContext({
|
||||
environment: validatedEnvironment,
|
||||
output: artifact.output,
|
||||
});
|
||||
return {
|
||||
context,
|
||||
logArtifactId: artifact.logArtifactId,
|
||||
dispose: () => artifact.dispose(),
|
||||
};
|
||||
} catch (error) {
|
||||
await Promise.resolve(artifact.dispose()).catch(() => undefined);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user