@@ -75,6 +75,7 @@ import {
7575 isRootCATrusted ,
7676 trustRootCA ,
7777} from 'src/lib/certificate-manager' ;
78+ import { download } from 'src/lib/download' ;
7879import { simplifyErrorForDisplay } from 'src/lib/error-formatting' ;
7980import { buildFeatureFlags } from 'src/lib/feature-flags' ;
8081import { getImageData } from 'src/lib/get-image-data' ;
@@ -186,7 +187,13 @@ export { getDefaultSiteDirectory, saveDefaultSiteDirectory };
186187
187188export { importSite , exportSite } from 'src/modules/import-export/lib/ipc-handlers' ;
188189
189- export { getUserDeskConfig , saveUserDeskConfig } from 'src/modules/desks/lib/ipc-handlers' ;
190+ export {
191+ getSiteDeskConfig ,
192+ getUserDeskConfig ,
193+ saveSiteDeskConfig ,
194+ saveUserDeskConfig ,
195+ } from 'src/modules/desks/lib/ipc-handlers' ;
196+ export { fetchSiteRest as fetchSiteRestApi } from 'src/lib/wordpress-rest-api' ;
190197
191198export {
192199 studioCodeSendMessage ,
@@ -678,6 +685,16 @@ export async function createSite(
678685 const metric = getBlueprintMetric ( blueprint ?. slug ) ;
679686 bumpStat ( StatsGroup . STUDIO_SITE_CREATE , metric ) ;
680687
688+ // If the blueprint has a bundle_url (API blueprints with bundled resources like zips),
689+ // download and extract the bundle so bundled resources can be resolved locally.
690+ let bundleTempDir : string | undefined ;
691+ let blueprintFilePath = blueprint ?. filePath ;
692+ if ( blueprint ?. bundle_url && ! blueprintFilePath ) {
693+ const result = await downloadAndExtractBlueprintBundle ( blueprint . bundle_url ) ;
694+ bundleTempDir = result . tempDir ;
695+ blueprintFilePath = result . blueprintJsonPath ;
696+ }
697+
681698 try {
682699 const { server } = await SiteServer . create (
683700 {
@@ -689,7 +706,7 @@ export async function createSite(
689706 enableHttps,
690707 siteId,
691708 blueprint : blueprint ?. blueprint ,
692- originalBlueprintPath : blueprint ?. filePath ,
709+ originalBlueprintPath : blueprintFilePath ,
693710 adminUsername,
694711 adminPassword,
695712 adminEmail,
@@ -748,7 +765,9 @@ export async function createSite(
748765
749766 throw error ;
750767 } finally {
751- if ( blueprint ?. filePath ) {
768+ if ( bundleTempDir ) {
769+ await removeBlueprintTempDir ( bundleTempDir ) . catch ( ( ) => { } ) ;
770+ } else if ( blueprint ?. filePath ) {
752771 const blueprintDir = nodePath . dirname ( nodePath . resolve ( blueprint . filePath ) ) ;
753772 await removeBlueprintTempDir ( blueprintDir ) . catch ( ( ) => { } ) ;
754773 }
@@ -2035,6 +2054,59 @@ export async function listLocalFileTree(
20352054 }
20362055}
20372056
2057+ /**
2058+ * Downloads a blueprint bundle zip from a URL, extracts it to a temp directory,
2059+ * and returns the path to the extracted blueprint.json.
2060+ * Used for API blueprints that reference bundled resources (e.g. theme zips, WXR files).
2061+ */
2062+ async function downloadAndExtractBlueprintBundle ( bundleUrl : string ) : Promise < {
2063+ blueprintJsonPath : string ;
2064+ tempDir : string ;
2065+ } > {
2066+ const tempDir = await fsPromises . mkdtemp (
2067+ nodePath . join ( os . tmpdir ( ) , 'studio-blueprint-bundle-' )
2068+ ) ;
2069+ const tempZipPath = nodePath . join ( tempDir , 'bundle.zip' ) ;
2070+
2071+ try {
2072+ await download ( bundleUrl , tempZipPath ) ;
2073+ await extractZip ( tempZipPath , tempDir ) ;
2074+ await fsPromises . unlink ( tempZipPath ) . catch ( ( ) => { } ) ;
2075+
2076+ // Find blueprint.json in the extracted contents
2077+ let blueprintJsonPath = nodePath . join ( tempDir , 'blueprint.json' ) ;
2078+ try {
2079+ await fsPromises . access ( blueprintJsonPath ) ;
2080+ } catch {
2081+ // Some zips have a single root directory — check one level deeper
2082+ const files = await fsPromises . readdir ( tempDir ) ;
2083+ for ( const file of files ) {
2084+ const nestedPath = nodePath . join ( tempDir , file , 'blueprint.json' ) ;
2085+ try {
2086+ await fsPromises . access ( nestedPath ) ;
2087+ blueprintJsonPath = nestedPath ;
2088+ break ;
2089+ } catch {
2090+ // continue checking
2091+ }
2092+ }
2093+ }
2094+
2095+ try {
2096+ await fsPromises . access ( blueprintJsonPath ) ;
2097+ } catch {
2098+ throw new Error (
2099+ 'No blueprint.json found in the downloaded bundle. Ensure the bundle zip contains a blueprint.json.'
2100+ ) ;
2101+ }
2102+
2103+ return { blueprintJsonPath, tempDir } ;
2104+ } catch ( error ) {
2105+ await fsPromises . rm ( tempDir , { recursive : true , force : true } ) . catch ( ( ) => { } ) ;
2106+ throw error ;
2107+ }
2108+ }
2109+
20382110export async function validateBlueprint (
20392111 _event : IpcMainInvokeEvent ,
20402112 blueprintJson : Blueprint [ 'blueprint' ]
0 commit comments