Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/runtime-cloudflare/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ The D1 profile registers the configured site identity and creates or converges o

Before canonical promotion, the operation checkpoints the exact prepared R2 pointer and expected coordinator version. Recovery skips SSI only when the coordinator's immutable commit receipt matches that complete pointer; a version match alone cannot claim another mutation's commit. The operation remains `publication-pending` after canonical commit until the corresponding immutable R2 publication receipt records promotion, supersession, or orphaning. Its terminal receipt identifies the artifact digest, SSI report, canonical pointer/version, publication identity, site URL, and canonical and terminal completion times. Profiles without a D1 operation repository retain the direct import transaction.

The import transaction boots a dedicated runtime with the pinned Static Site Importer v1.3.4 archive (`8d27286021d7c6141609def40a97591322a14340b23a17d9405f7919ea145a29`) from R2, invokes its public `static-site-importer/import-website-artifact` ability as the operator-authorized administrator, and requires the canonical quality gate plus zero fallback, core HTML, freeform, and invalid blocks. SSI and its MU loader are runtime-owned and excluded from canonical mutable `wp-content`; generated themes, pages, options, and assets persist normally through MDI/R2. Failed or partial imports discard the PHP runtime without committing. Successful imports persist compact provenance, enqueue affected publication routes, and store at most 20 idempotency receipts in canonical options as a second mutation fence behind the D1 operation record. Exact replay converges without a new revision; reuse with different input returns a conflict.
The import transaction boots a dedicated runtime generated from the pinned Static Site Importer v1.3.4 release archive (`8d27286021d7c6141609def40a97591322a14340b23a17d9405f7919ea145a29`) and stored content-addressably in R2. The generated runtime retains SSI, its website-artifact dependencies, and the PHP transformer while excluding Figma-only code, fixtures, tests, tools, and documentation that the Cloudflare website-import contract cannot execute. It invokes SSI's public `static-site-importer/import-website-artifact` ability as the operator-authorized administrator and requires the canonical quality gate plus zero fallback, core HTML, freeform, and invalid blocks. SSI and its MU loader are runtime-owned and excluded from canonical mutable `wp-content`; generated themes, pages, options, and assets persist normally through MDI/R2. Failed or partial imports discard the PHP runtime without committing. Successful imports persist compact provenance, enqueue affected publication routes, and store at most 20 idempotency receipts in canonical options as a second mutation fence behind the D1 operation record. Exact replay converges without a new revision; reuse with different input returns a conflict.

SSI is extracted only for import requests, so normal browser, mutation, publication, and cron boots retain their existing memory and latency profile. The pinned normal plugin archive bundles Blocks Engine and supports website artifacts without the optional Figma zstd extension; compressed `.fig` import is outside this runtime contract.

Expand Down

This file was deleted.

35 changes: 35 additions & 0 deletions packages/runtime-cloudflare/assets/website-importer-artifact.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"schema": "wp-codebox/runtime-archive-artifact/v1",
"name": "website-importer",
"key": "runtime/archives/website-importer/c5aa5c2ef67503ed94c7b249b2bf102630f279f985ece44245df83169fa75e27.zip",
"archive": {
"sha256": "c5aa5c2ef67503ed94c7b249b2bf102630f279f985ece44245df83169fa75e27",
"size": 1739484
},
"source": {
"url": "https://github.com/Automattic/static-site-importer/releases/download/v1.3.4/static-site-importer.zip",
"version": "1.3.4",
"identity": "08b9dd650f3c3161c5b350796a5db6ef083516ae"
},
"component": {
"schema": "wp-codebox/runtime-archive-component/v1",
"id": "website-importer",
"package": {
"profile": "website-artifact-import",
"root": "static-site-importer"
},
"wordpress": {
"install_path": "plugins/static-site-importer",
"bootstrap_file": "static-site-importer.php",
"loader_path": "mu-plugins/wp-codebox-runtime-component-website-importer.php",
"version_constant": "STATIC_SITE_IMPORTER_VERSION"
},
"abilities": {
"website-artifact-import": "static-site-importer/import-website-artifact"
},
"limits": {
"files": 10000,
"bytes": 67108864
}
}
}
53 changes: 53 additions & 0 deletions packages/runtime-cloudflare/components/website-importer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
{
"schema": "wp-codebox/runtime-archive-component-source/v1",
"source": {
"url": "https://github.com/Automattic/static-site-importer/releases/download/v1.3.4/static-site-importer.zip",
"version": "1.3.4",
"identity": "08b9dd650f3c3161c5b350796a5db6ef083516ae",
"sha256": "8d27286021d7c6141609def40a97591322a14340b23a17d9405f7919ea145a29"
},
"component": {
"schema": "wp-codebox/runtime-archive-component/v1",
"id": "website-importer",
"package": {
"profile": "website-artifact-import",
"root": "static-site-importer"
},
"wordpress": {
"install_path": "plugins/static-site-importer",
"bootstrap_file": "static-site-importer.php",
"loader_path": "mu-plugins/wp-codebox-runtime-component-website-importer.php",
"version_constant": "STATIC_SITE_IMPORTER_VERSION"
},
"abilities": {
"website-artifact-import": "static-site-importer/import-website-artifact"
},
"limits": {
"files": 10000,
"bytes": 67108864
}
},
"migration": {
"source_sha256": "8d27286021d7c6141609def40a97591322a14340b23a17d9405f7919ea145a29",
"selectors": [
{ "type": "file", "path": "static-site-importer.php" },
{ "type": "prefix", "path": "blocks/" },
{ "type": "prefix", "path": "includes/" },
{ "type": "prefix", "path": "lib/" },
{ "type": "prefix", "path": "registry/" },
{ "type": "file", "path": "vendor/autoload.php" },
{ "type": "prefix", "path": "vendor/composer/" },
{ "type": "prefix", "path": "vendor/automattic/blocks-engine-php-transformer/php-transformer/" },
{ "type": "prefix", "path": "vendor/dflydev/" },
{ "type": "prefix", "path": "vendor/league/" },
{ "type": "prefix", "path": "vendor/nette/" },
{ "type": "prefix", "path": "vendor/psr/" },
{ "type": "prefix", "path": "vendor/symfony/" }
],
"required_files": [
"static-site-importer.php",
"vendor/autoload.php",
"vendor/automattic/blocks-engine-php-transformer/php-transformer/php-transformer.php"
]
}
}
5 changes: 5 additions & 0 deletions packages/runtime-cloudflare/src/publication-response.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export function isCacheablePublicationResponse(response: Response): boolean {
if (response.headers.has("set-cookie")) return false
if (response.status === 200) return response.headers.get("content-type")?.includes("text/html") ?? false
return (response.status === 301 || response.status === 308) && response.headers.has("location")
}
5 changes: 4 additions & 1 deletion packages/runtime-cloudflare/src/runtime-archive-artifact.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export interface RuntimeArchiveArtifactManifest {
key: string
archive: { sha256: string; size: number }
source: { url: string; version?: string; identity?: string }
component?: RuntimeArchiveComponent
}

export function runtimeArchiveArtifactKey(name: string, sha256: string): string {
Expand All @@ -20,6 +21,7 @@ export function validateRuntimeArchiveArtifactManifest(manifest: RuntimeArchiveA
if (manifest.key !== runtimeArchiveArtifactKey(manifest.name, manifest.archive.sha256)) throw new Error("Runtime archive artifact key is not content addressed.")
if (!Number.isSafeInteger(manifest.archive.size) || manifest.archive.size < 1 || manifest.archive.size > RUNTIME_ARCHIVE_MAX_BYTES) throw new Error("Runtime archive artifact size is outside the allowed budget.")
if (!manifest.source.url.startsWith("https://")) throw new Error("Runtime archive artifact source URL is invalid.")
if (manifest.component) runtimeArchiveComponent(manifest.component)
}

export async function readRuntimeArchiveArtifact(bucket: R2Bucket, manifest: RuntimeArchiveArtifactManifest): Promise<Uint8Array> {
Expand All @@ -33,6 +35,7 @@ export async function readRuntimeArchiveArtifact(bucket: R2Bucket, manifest: Run
}

async function sha256Hex(bytes: Uint8Array): Promise<string> {
const digest = await crypto.subtle.digest("SHA-256", Uint8Array.from(bytes).buffer)
const digest = await crypto.subtle.digest("SHA-256", bytes as Uint8Array<ArrayBuffer>)
return Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0")).join("")
}
import { runtimeArchiveComponent, type RuntimeArchiveComponent } from "../../runtime-core/src/runtime-archive-component.js"
Loading