Skip to content

Commit 30e4ac9

Browse files
authored
Merge pull request #1236 from Automattic/cook/materialization-artifact-ref-helpers
Add materialization artifact ref helpers
2 parents dca900c + 5c36c45 commit 30e4ac9

2 files changed

Lines changed: 108 additions & 23 deletions

File tree

packages/runtime-core/src/materialization-contracts.ts

Lines changed: 78 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import type { RuntimeRunArtifactRef } from "./run-registry.js"
22

33
export const MATERIALIZATION_RESULT_SCHEMA = "wp-codebox/materialization-result/v1" as const
4+
export const BROWSER_ARTIFACT_PERSISTENCE_PROJECTION_SCHEMA = "wp-codebox/browser-artifact-persistence-projection/v1" as const
5+
export const ARTIFACT_BUNDLE_FILE_MANIFEST_SCHEMA = "wp-codebox/artifact-bundle-file-manifest/v1" as const
46

57
export interface MaterializationArtifactRef {
68
kind: string
@@ -91,14 +93,21 @@ export interface BrowserArtifactProjectionInput {
9193
}
9294

9395
export interface BrowserArtifactPersistenceProjection {
94-
schema: "wp-codebox/browser-artifact-persistence-projection/v1"
96+
schema: typeof BROWSER_ARTIFACT_PERSISTENCE_PROJECTION_SCHEMA
9597
artifact?: Record<string, unknown>
9698
artifacts: Record<string, unknown>[]
9799
artifactBundle?: Record<string, unknown>
98100
materialization?: Record<string, unknown>
99101
artifactRefs: MaterializationArtifactRef[]
100102
}
101103

104+
export interface ArtifactBundleFileManifest {
105+
schema: typeof ARTIFACT_BUNDLE_FILE_MANIFEST_SCHEMA
106+
bundle?: MaterializationArtifactRef
107+
files: MaterializationArtifactRef[]
108+
paths: string[]
109+
}
110+
102111
export function materializationPhaseResult(input: Omit<MaterializationPhaseResult, "schema" | "artifactRefs"> & { artifactRefs?: MaterializationArtifactRef[] }): MaterializationPhaseResult {
103112
return stripUndefined({
104113
schema: "wp-codebox/materialization-phase-result/v1" as const,
@@ -124,7 +133,7 @@ export function materializationResultEnvelope(input: {
124133
}): MaterializationResultEnvelope {
125134
const phases = input.phases ?? []
126135
const status = input.status ?? (input.error || phases.some((phase) => phase.status === "failed") ? "failed" : "completed")
127-
const artifactRefs = [...(input.artifactRefs ?? []), ...phases.flatMap((phase) => phase.artifactRefs)]
136+
const artifactRefs = normalizeMaterializationArtifactRefs([...(input.artifactRefs ?? []), ...phases.flatMap((phase) => phase.artifactRefs)])
128137
const diagnostics = input.diagnostics ?? phases.flatMap(phaseDiagnostics)
129138
const base = stripUndefined({
130139
schema: MATERIALIZATION_RESULT_SCHEMA,
@@ -233,12 +242,71 @@ export function browserArtifactPersistenceProjection(input: BrowserArtifactProje
233242
const artifactRefs = browserArtifactProjectionRefs({ artifactBundle, artifacts, materialization })
234243

235244
return stripUndefined({
236-
schema: "wp-codebox/browser-artifact-persistence-projection/v1" as const,
245+
schema: BROWSER_ARTIFACT_PERSISTENCE_PROJECTION_SCHEMA,
237246
artifact,
238247
artifacts,
239248
artifactBundle,
240249
materialization,
241-
artifactRefs,
250+
artifactRefs: normalizeMaterializationArtifactRefs(artifactRefs),
251+
})
252+
}
253+
254+
export function normalizeMaterializationArtifactRef(input: unknown, defaults: Partial<MaterializationArtifactRef> = {}): MaterializationArtifactRef | undefined {
255+
const source = asRecord(input)
256+
if (!source) {
257+
return undefined
258+
}
259+
260+
const kind = stringValue(source.kind ?? source.artifact_type ?? source.role) || defaults.kind
261+
const id = stringValue(source.id ?? source.artifact_id ?? source.artifactId) || defaults.id
262+
const path = stringValue(source.path ?? source.artifacts_path ?? source.artifactsPath ?? source.directory) || defaults.path
263+
const digest = normalizeDigest(source.digest ?? source.sha256 ?? source.contentDigest ?? source.content_digest) ?? defaults.digest
264+
265+
if (!id && !path && !digest && !stringValue(source.kind ?? source.artifact_type ?? source.role)) {
266+
return undefined
267+
}
268+
269+
return stripUndefined({
270+
kind: kind ?? "artifact",
271+
id,
272+
path,
273+
digest,
274+
})
275+
}
276+
277+
export function normalizeMaterializationArtifactRefs(inputs: unknown[] | undefined): MaterializationArtifactRef[] {
278+
const refs: MaterializationArtifactRef[] = []
279+
const seen = new Set<string>()
280+
for (const input of inputs ?? []) {
281+
const ref = normalizeMaterializationArtifactRef(input)
282+
if (!ref) {
283+
continue
284+
}
285+
const key = `${ref.kind}\u0000${ref.id ?? ""}\u0000${ref.path ?? ""}\u0000${ref.digest?.algorithm ?? ""}\u0000${ref.digest?.value ?? ""}`
286+
if (!seen.has(key)) {
287+
refs.push(ref)
288+
seen.add(key)
289+
}
290+
}
291+
return refs
292+
}
293+
294+
export function persistedBrowserArtifactRefs(input: BrowserArtifactProjectionInput | MaterializationResultEnvelope | unknown): MaterializationArtifactRef[] {
295+
return browserArtifactPersistenceProjection(input).artifactRefs
296+
}
297+
298+
export function artifactBundleFileManifest(input: BrowserArtifactProjectionInput | MaterializationResultEnvelope | BrowserArtifactPersistenceProjection | unknown): ArtifactBundleFileManifest {
299+
const projection = isRecord(input) && input.schema === BROWSER_ARTIFACT_PERSISTENCE_PROJECTION_SCHEMA
300+
? input as unknown as BrowserArtifactPersistenceProjection
301+
: browserArtifactPersistenceProjection(input)
302+
const refs = normalizeMaterializationArtifactRefs(projection.artifactRefs)
303+
const bundle = refs.find((ref) => ref.kind === "artifact-bundle")
304+
const files = refs.filter((ref) => ref.path && ref.kind !== "artifact-bundle" && ref.kind !== "materialization")
305+
return stripUndefined({
306+
schema: ARTIFACT_BUNDLE_FILE_MANIFEST_SCHEMA,
307+
bundle,
308+
files,
309+
paths: files.map((file) => file.path as string),
242310
})
243311
}
244312

@@ -272,29 +340,16 @@ function materializationProjectionSource(input: unknown): Record<string, unknown
272340

273341
function browserArtifactProjectionRefs(input: { artifactBundle?: Record<string, unknown>; artifacts: Record<string, unknown>[]; materialization?: Record<string, unknown> }): MaterializationArtifactRef[] {
274342
const refs: MaterializationArtifactRef[] = []
275-
const bundleId = stringValue(input.artifactBundle?.id ?? input.artifactBundle?.artifact_id)
276-
const bundleDigest = normalizeDigest(input.artifactBundle?.contentDigest ?? input.artifactBundle?.content_digest ?? input.artifactBundle?.digest ?? input.artifactBundle?.sha256)
277-
if (bundleId || bundleDigest || stringValue(input.artifactBundle?.path)) {
278-
refs.push(stripUndefined({
279-
kind: "artifact-bundle",
280-
id: bundleId || undefined,
281-
path: stringValue(input.artifactBundle?.path) || stringValue(input.artifactBundle?.directory) || undefined,
282-
digest: bundleDigest,
283-
}))
343+
const bundle = normalizeMaterializationArtifactRef(input.artifactBundle, { kind: "artifact-bundle" })
344+
if (bundle) {
345+
refs.push(bundle)
284346
}
285347

286348
for (const artifact of input.artifacts) {
287-
const path = stringValue(artifact.path)
288-
const id = stringValue(artifact.id ?? artifact.artifact_id)
289-
if (!path && !id) {
290-
continue
349+
const ref = normalizeMaterializationArtifactRef(artifact, { kind: "browser-artifact" })
350+
if (ref?.path || ref?.id) {
351+
refs.push(ref)
291352
}
292-
refs.push(stripUndefined({
293-
kind: stringValue(artifact.kind ?? artifact.artifact_type ?? artifact.role) || "browser-artifact",
294-
id: id || undefined,
295-
path: path || undefined,
296-
digest: normalizeDigest(artifact.digest ?? artifact.sha256 ?? artifact.contentDigest ?? artifact.content_digest),
297-
}))
298353
}
299354

300355
const materializationId = stringValue(input.materialization?.id ?? input.materialization?.artifact_id)

tests/browser-callback-materialization-contracts.test.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import assert from "node:assert/strict"
22
import { createServer } from "node:http"
33
import {
4+
artifactBundleFileManifest,
45
browserArtifactGrant,
56
browserArtifactPersistenceProjection,
67
browserArtifactRef,
@@ -10,7 +11,9 @@ import {
1011
materializationResultEnvelope,
1112
materializationPhaseResult,
1213
materializationRunArtifactRefs,
14+
normalizeMaterializationArtifactRefs,
1315
normalizeMaterializationResultEnvelope,
16+
persistedBrowserArtifactRefs,
1417
trustedBrowserSessionOrigin,
1518
trustedBrowserSessionOrigins,
1619
verifyBrowserCallbackSignature,
@@ -215,6 +218,33 @@ assert.deepEqual(projection.artifactRefs, [
215218
id: "materialization-1",
216219
},
217220
])
221+
assert.deepEqual(persistedBrowserArtifactRefs(projection), projection.artifactRefs)
222+
assert.deepEqual(browserArtifactPersistenceProjection(projection).artifactRefs, projection.artifactRefs)
223+
assert.deepEqual(normalizeMaterializationArtifactRefs([
224+
{ kind: "browser-html", path: "files/browser/index.html", sha256: "def" },
225+
{ role: "browser-html", path: "files/browser/index.html", content_digest: "def" },
226+
{ artifact_type: "browser-screenshot", artifact_id: "screenshot-1", artifacts_path: "files/browser/screenshot.png", contentDigest: { algorithm: "sha256", value: "123" } },
227+
]), [
228+
{ kind: "browser-html", path: "files/browser/index.html", digest: { algorithm: "sha256", value: "def" } },
229+
{ kind: "browser-screenshot", id: "screenshot-1", path: "files/browser/screenshot.png", digest: { algorithm: "sha256", value: "123" } },
230+
])
231+
assert.deepEqual(artifactBundleFileManifest(projection), {
232+
schema: "wp-codebox/artifact-bundle-file-manifest/v1",
233+
bundle: {
234+
kind: "artifact-bundle",
235+
id: "artifact-bundle-sha256-abc",
236+
path: "artifacts/run-1",
237+
digest: { algorithm: "sha256", value: "abc" },
238+
},
239+
files: [
240+
{
241+
kind: "browser-html",
242+
path: "files/browser/index.html",
243+
digest: { algorithm: "sha256", value: "def" },
244+
},
245+
],
246+
paths: ["files/browser/index.html"],
247+
})
218248
const callbackEnvelope = browserCallbackResultEnvelope({
219249
capability: "persist-browser-artifact",
220250
ability: "wp-codebox/persist-browser-artifact",

0 commit comments

Comments
 (0)