-
Notifications
You must be signed in to change notification settings - Fork 0
Emit VISOR artifact bundle #466
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,141 @@ | ||
| import type { | ||
| UiNodeKind, | ||
| UiSceneIr, | ||
| UiSceneLowerMode, | ||
| UiSceneLoweringOptions, | ||
| UiTargetProfile, | ||
| UiTextRef, | ||
| } from './ui-scene-ir.js'; | ||
| import type { | ||
| BijouBlockArtifact, | ||
| GraphqlBijouBlockDebugSummary, | ||
| } from './graphql-bijou-block.part01.js'; | ||
|
|
||
| export const VISOR_ARTIFACT_BUNDLE_VERSION = 'visor-artifact-bundle/1' as const; | ||
| export const VISOR_REPLAY_METADATA_VERSION = 'visor-replay-metadata/1' as const; | ||
| export const VISOR_VISUAL_SCENE_FACTS_VERSION = 'visor-visual-scene-facts/1' as const; | ||
|
|
||
| export type VisorArtifactBundleVersion = typeof VISOR_ARTIFACT_BUNDLE_VERSION; | ||
| export type VisorReplayMetadataVersion = typeof VISOR_REPLAY_METADATA_VERSION; | ||
| export type VisorVisualSceneFactsVersion = typeof VISOR_VISUAL_SCENE_FACTS_VERSION; | ||
|
|
||
| export interface CreateVisorArtifactBundleFromGraphqlOptions extends UiSceneLoweringOptions { | ||
| readonly fixtureId: string; | ||
| readonly sourceName: string; | ||
| readonly scenarioId?: string; | ||
| readonly deterministicSeed?: string; | ||
| } | ||
|
|
||
| export interface CreateVisorArtifactBundleInput extends CreateVisorArtifactBundleFromGraphqlOptions { | ||
| readonly sourceText: string; | ||
| readonly bijouBlock: BijouBlockArtifact; | ||
| readonly uiScene: UiSceneIr; | ||
| readonly debugSummary: GraphqlBijouBlockDebugSummary; | ||
| } | ||
|
|
||
| export interface VisorArtifactBundleFixture { | ||
| readonly id: string; | ||
| readonly sourceName: string; | ||
| readonly sourceHash: string; | ||
| readonly normalizedSourceHash: string; | ||
| } | ||
|
|
||
| export interface VisorArtifactBundleSource { | ||
| readonly language: 'graphql'; | ||
| readonly text: string; | ||
| } | ||
|
|
||
| export interface VisorArtifactBundleArtifacts { | ||
| readonly bijouBlock: BijouBlockArtifact; | ||
| readonly uiScene: UiSceneIr; | ||
| readonly debugSummary: GraphqlBijouBlockDebugSummary; | ||
| } | ||
|
|
||
| export interface VisorReplayStep { | ||
| readonly id: string; | ||
| readonly label: string; | ||
| readonly inputHashes: readonly string[]; | ||
| readonly outputHashes: readonly string[]; | ||
| } | ||
|
|
||
| export interface VisorReplayMetadata { | ||
| readonly replayVersion: VisorReplayMetadataVersion; | ||
| readonly scenarioId: string; | ||
| readonly fixtureId: string; | ||
| readonly deterministicSeed: string; | ||
| readonly consumedHashes: { | ||
| readonly sourceHash: string; | ||
| readonly normalizedSourceHash: string; | ||
| readonly artifactHash: string; | ||
| readonly sceneHash: string; | ||
| readonly debugSummaryHash: string; | ||
| readonly visualFactsHash: string; | ||
| }; | ||
| readonly steps: readonly VisorReplayStep[]; | ||
| } | ||
|
|
||
| export interface VisorVisualNodeFact { | ||
| readonly nodeId: string; | ||
| readonly kind: UiNodeKind; | ||
| readonly role?: string; | ||
| readonly component?: string; | ||
| readonly parentId?: string; | ||
| readonly childNodeIds: readonly string[]; | ||
| readonly text?: UiTextRef; | ||
| readonly i18nKeys: readonly string[]; | ||
| readonly tokenRefs: readonly string[]; | ||
| readonly actionIds: readonly string[]; | ||
| readonly bindingIds: readonly string[]; | ||
| readonly sourceRefs: readonly string[]; | ||
| } | ||
|
|
||
| export interface VisorVisualLowerModeFact { | ||
| readonly mode: UiSceneLowerMode; | ||
| readonly surfaceHash: string; | ||
| readonly rowsHash: string; | ||
| } | ||
|
|
||
| export interface VisorVisualSceneFacts { | ||
| readonly visualFactsVersion: VisorVisualSceneFactsVersion; | ||
| readonly sceneId: string; | ||
| readonly rootNodeId: string; | ||
| readonly targetProfiles: readonly UiTargetProfile[]; | ||
| readonly nodeFacts: readonly VisorVisualNodeFact[]; | ||
| readonly lowerModes: readonly VisorVisualLowerModeFact[]; | ||
| } | ||
|
|
||
| export interface VisorArtifactBundleHashes { | ||
| readonly sourceHash: string; | ||
| readonly normalizedSourceHash: string; | ||
| readonly artifactHash: string; | ||
| readonly sceneHash: string; | ||
| readonly debugSummaryHash: string; | ||
| readonly replayHash: string; | ||
| readonly visualFactsHash: string; | ||
| readonly bundleHash: string; | ||
| } | ||
|
|
||
| export interface VisorArtifactBundleReceipt { | ||
| readonly subject: string; | ||
| readonly version: string; | ||
| readonly hash: string; | ||
| } | ||
|
|
||
| export interface VisorArtifactBundle { | ||
| readonly bundleVersion: VisorArtifactBundleVersion; | ||
| readonly fixture: VisorArtifactBundleFixture; | ||
| readonly source: VisorArtifactBundleSource; | ||
| readonly artifacts: VisorArtifactBundleArtifacts; | ||
| readonly replay: VisorReplayMetadata; | ||
| readonly visual: VisorVisualSceneFacts; | ||
| readonly hashes: VisorArtifactBundleHashes; | ||
| readonly receipts: readonly VisorArtifactBundleReceipt[]; | ||
| } | ||
|
|
||
| export type VisorArtifactBundleHashBody = Omit<VisorArtifactBundleHashes, 'bundleHash'>; | ||
|
|
||
| export type VisorArtifactBundleWithoutHash = Omit<VisorArtifactBundle, 'hashes'> & { | ||
| readonly hashes: VisorArtifactBundleHashBody & { | ||
| readonly bundleHash?: string; | ||
| }; | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,111 @@ | ||
| import { hashUiSceneValue } from './ui-scene-ir.js'; | ||
| import { compileGraphqlBijouBlock } from './graphql-bijou-block.part02.js'; | ||
| import { lowerBijouBlockToUiScene } from './graphql-bijou-block.part03.js'; | ||
| import { createGraphqlBijouBlockDebugSummary } from './graphql-bijou-block.part04.js'; | ||
| import { normalizeSourceName } from './graphql-bijou-block.part10.js'; | ||
| import { | ||
| VISOR_ARTIFACT_BUNDLE_VERSION, | ||
| type CreateVisorArtifactBundleFromGraphqlOptions, | ||
| type CreateVisorArtifactBundleInput, | ||
| type VisorArtifactBundle, | ||
| type VisorArtifactBundleHashBody, | ||
| } from './visor-artifact-bundle.part01.js'; | ||
| import { | ||
| createVisorReplayMetadata, | ||
| createVisorVisualSceneFacts, | ||
| } from './visor-artifact-bundle.part03.js'; | ||
| import { | ||
| assertDebugSummaryMatchesFacts, | ||
| assertSceneMatchesArtifact, | ||
| createReceipts, | ||
| normalizeLogicalId, | ||
| semanticBundleHashPayload, | ||
| } from './visor-artifact-bundle.part04.js'; | ||
|
|
||
| export function createVisorArtifactBundleFromGraphql( | ||
| source: string, | ||
| options: CreateVisorArtifactBundleFromGraphqlOptions, | ||
| ): VisorArtifactBundle { | ||
| const sourceName = normalizeSourceName(options.sourceName); | ||
| const bijouBlock = compileGraphqlBijouBlock(source, { sourceName }); | ||
| const uiScene = lowerBijouBlockToUiScene(bijouBlock); | ||
| const debugSummary = createGraphqlBijouBlockDebugSummary(bijouBlock, options); | ||
| return createVisorArtifactBundle({ | ||
| ...options, | ||
| sourceName, | ||
| sourceText: source, | ||
| bijouBlock, | ||
| uiScene, | ||
| debugSummary, | ||
| }); | ||
| } | ||
|
|
||
| export function createVisorArtifactBundle(input: CreateVisorArtifactBundleInput): VisorArtifactBundle { | ||
| const fixtureId = normalizeLogicalId(input.fixtureId, 'fixtureId'); | ||
| const scenarioId = normalizeLogicalId(input.scenarioId ?? `${fixtureId}:graphql-to-ui-scene`, 'scenarioId'); | ||
| const deterministicSeed = input.deterministicSeed ?? 'no-randomness'; | ||
| const sourceName = normalizeSourceName(input.sourceName); | ||
|
|
||
| if (input.bijouBlock.sourceName !== sourceName) { | ||
| throw new Error('visor-artifact-bundle/1 sourceName does not match bijou-block/1.'); | ||
| } | ||
|
|
||
| const visual = createVisorVisualSceneFacts(input.uiScene, input.debugSummary); | ||
| const visualFactsHash = hashUiSceneValue(visual); | ||
| assertSceneMatchesArtifact(input.bijouBlock, input.uiScene); | ||
| assertDebugSummaryMatchesFacts(input.bijouBlock, input.uiScene, input.debugSummary); | ||
|
|
||
| const sourceHash = hashUiSceneValue(input.sourceText); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
For direct Useful? React with 👍 / 👎. |
||
| const normalizedSourceHash = input.bijouBlock.sourceHash; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
For direct Useful? React with 👍 / 👎. |
||
| const artifactHash = hashUiSceneValue(input.bijouBlock); | ||
| const sceneHash = hashUiSceneValue(input.uiScene); | ||
| const debugSummaryHash = input.debugSummary.summaryHash; | ||
| const replay = createVisorReplayMetadata({ | ||
| fixtureId, | ||
| scenarioId, | ||
| deterministicSeed, | ||
| sourceHash, | ||
| normalizedSourceHash, | ||
| artifactHash, | ||
| sceneHash, | ||
| debugSummaryHash, | ||
| visualFactsHash, | ||
| }); | ||
| const replayHash = hashUiSceneValue(replay); | ||
| const hashes: VisorArtifactBundleHashBody = { | ||
| sourceHash, | ||
| normalizedSourceHash, | ||
| artifactHash, | ||
| sceneHash, | ||
| debugSummaryHash, | ||
| replayHash, | ||
| visualFactsHash, | ||
| }; | ||
| const bundleWithoutHash = { | ||
| bundleVersion: VISOR_ARTIFACT_BUNDLE_VERSION, | ||
| fixture: { id: fixtureId, sourceName, sourceHash, normalizedSourceHash }, | ||
| source: { language: 'graphql' as const, text: input.sourceText }, | ||
| artifacts: { | ||
| bijouBlock: input.bijouBlock, | ||
| uiScene: input.uiScene, | ||
| debugSummary: input.debugSummary, | ||
| }, | ||
| replay, | ||
| visual, | ||
| hashes: { ...hashes, bundleHash: undefined }, | ||
| receipts: createReceipts({ | ||
| artifactHash, | ||
| sceneHash, | ||
| debugSummaryHash, | ||
| replayHash, | ||
| visualFactsHash, | ||
| }), | ||
| }; | ||
| return { | ||
| ...bundleWithoutHash, | ||
| hashes: { | ||
| ...hashes, | ||
| bundleHash: hashUiSceneValue(semanticBundleHashPayload(bundleWithoutHash)), | ||
| }, | ||
| }; | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When callers provide
deterministicSeed, this value is copied into replay metadata without the logical-id or host-local checks used for fixture/scenario ids. Passing a seed such as/tmp/run-123orpid:...emits the absolute-path or process-local replay facts the bundle contract says must be rejected, so VISOR artifacts can become non-portable while still passing the builder.Useful? React with 👍 / 👎.