PluginProbe
Extendify / 3.2.2
Extendify v3.2.2
3.2.2 3.2.1 3.2.0 3.1.6 3.1.5 3.1.4 3.1.3 3.1.2 3.1.1 3.1.0 3.0.6 3.0.5 3.0.4 trunk 0.1.0 0.10.0 0.10.1 0.10.2 0.11.0 0.11.1 0.2.0 0.3.0 0.3.1 0.4.0 0.5.0 All 128 releases
← All changes | src/AutoLaunch/hooks/useCreateSite.js +164 -89 3.0.4 → 3.2.2 View file →
@@ -1,7 +1,8 @@
1 1 import { handleDesignBuild } from '@auto-launch/fetchers/get-design-build';
2 2 import { handleHome } from '@auto-launch/fetchers/get-home';
3 3 import { handleSiteImages } from '@auto-launch/fetchers/get-images';
4 +import { handleLaunchDecisions } from '@auto-launch/fetchers/get-launch-decisions';
4 5 import { handleSiteLogo } from '@auto-launch/fetchers/get-logo';
5 6 import { handlePages } from '@auto-launch/fetchers/get-pages';
6 7 import { handleSitePlugins } from '@auto-launch/fetchers/get-plugins';
7 8 import { handleSiteProfile } from '@auto-launch/fetchers/get-profile';
@@ -18,10 +19,13 @@
18 19 updateSinglePageLinksToSections,
19 20 } from '@auto-launch/functions/links';
20 21 import {
21 22 addPageLinksToNav,
23 + addSectionLinksFromDesign,
22 24 addSectionLinksToNav,
23 25 createNavigation,
26 + injectNavExtras,
27 + injectWooCommerceIcons,
24 28 updateNavAttributes,
25 29 } from '@auto-launch/functions/nav';
26 30 import {
27 31 addImprintPage,
@@ -26,8 +30,9 @@
26 30 import {
27 31 addImprintPage,
28 32 createWpPages,
29 33 getPagesToCreate,
34 + isBlogPage,
30 35 PLUGIN_OWNED_PAGES,
31 36 setHelloWorldFeaturedImage,
32 37 updatePageTitlePattern,
33 38 } from '@auto-launch/functions/pages';
@@ -32,45 +37,49 @@
32 37 updatePageTitlePattern,
33 38 } from '@auto-launch/functions/pages';
34 39 import { generatePageContent } from '@auto-launch/functions/patterns';
35 40 import {
36 - activatePlugin,
37 41 alreadyActive,
42 + ensurePluginsActive,
38 43 getActivePlugins,
39 - installPlugin,
40 44 replacePlaceholderPatterns,
45 + reportFailedDependencies,
46 + reportInactivePlugins,
41 47 } from '@auto-launch/functions/plugins';
42 48 import {
43 49 postLaunchFunctions,
44 50 prefetchAssistData,
45 51 } from '@auto-launch/functions/setup';
52 +import { applySocialProfiles } from '@auto-launch/functions/social-links';
46 53 import {
47 54 setThemeRenderingMode,
48 55 updateTemplatePart,
49 56 updateVariation,
50 57 } from '@auto-launch/functions/theme';
51 -import { computeVibeAdjustedBlocks } from '@auto-launch/functions/vibes';
58 +import { computeVibeAdjustments } from '@auto-launch/functions/vibes';
52 59 import {
53 60 createBlogSampleData,
54 61 getOption,
55 62 getPageById,
63 + storeSiteImages,
56 64 updateOption,
57 65 } from '@auto-launch/functions/wp';
58 66 import { useWarnOnLeave } from '@auto-launch/hooks/useWarnOnLeave';
59 67 import { useLaunchDataStore } from '@auto-launch/state/launch-data';
68 +import { launchStrings } from '@auto-launch/strings';
69 +import { digest } from '@shared/api/digest';
70 +import { siteImageUrls } from '@shared/lib/site-images';
60 71 import { useAIConsentStore } from '@shared/state/ai-consent';
61 72 import { useEffect, useRef, useState } from '@wordpress/element';
62 73 import { __ } from '@wordpress/i18n';
63 74 import useSWRImmutable from 'swr/immutable';
64 75
65 -const { homeUrl, showImprint, wpLanguage, installedPluginsSlugs } =
66 - window.extSharedData;
76 +const { homeUrl, showImprint, wpLanguage } = window.extSharedData;
67 77
68 78 // TODO: I think a good strategy is "if something fails, try to refetch some state"
69 79
70 80 export const useCreateSite = () => {
71 - // All the data we need to finish
72 - const { setErrorMessage, addStatusMessage, needToStall, ...data } =
81 + const { setErrorMessage, addStatusMessage, needToStall, setData, ...data } =
73 82 useLaunchDataStore();
74 83 const { setUserGaveConsent } = useAIConsentStore();
75 84 const homeStretch = useRef(false);
76 85 const [warnOnReload, setWarnOnReload] = useState(!needToStall());
@@ -111,8 +120,11 @@
111 120 useRunStep(
112 121 'siteLogo',
113 122 () => {
114 123 if (!data.siteProfile?.title) return null;
124 + // Logo was already uploaded by handleDesignBuild;
125 + // generating an AI logo here is not necessary.
126 + if (data.designBuild?.logoUrl) return null;
115 127 return data;
116 128 },
117 129 async (params) => {
118 130 checkIn({ stage: 'get_logo' });
@@ -120,13 +132,12 @@
120 132 },
121 133 );
122 134
123 135 // needs: siteProfile
124 - // provides:sitePlugins: [{name, wordpressSlug}]
136 + // provides: sitePlugins: [{name, wordpressSlug}]
125 137 useRunStep(
126 138 'sitePlugins',
127 139 () => {
128 - // We just need the site profile, which has this
129 140 if (!data.siteProfile?.title) return null;
130 141 return data;
131 142 },
132 143 async (params) => {
@@ -136,24 +147,17 @@
136 147 );
137 148
138 149 useEffect(() => {
139 150 // Start installing the partner plugins asap
140 - if (!data.sitePlugins) return;
151 + if (!data.sitePlugins?.length) return;
141 152
142 - const pluginsToInstall = data.sitePlugins.filter(
143 - ({ wordpressSlug: slug }) => !installedPluginsSlugs?.includes(slug),
144 - );
145 - if (pluginsToInstall.length === 0) return;
146 153 setStatus(
147 154 // translators: this is for a action log UI. Keep it short
148 - __('Setting up functionality for your website', 'extendify-local'),
155 + launchStrings().statusFunctionality,
149 156 );
150 - (async function install() {
151 - for (const { wordpressSlug: slug } of pluginsToInstall) {
152 - const p = await installPlugin(slug);
153 - await activatePlugin(p?.plugin ?? slug);
154 - }
155 - })();
157 + ensurePluginsActive(
158 + data.sitePlugins.map(({ wordpressSlug }) => wordpressSlug),
159 + );
156 160 }, [data.sitePlugins]);
157 161
158 162 // needs: siteProfile
159 163 // provides: style: {}
@@ -159,9 +163,8 @@
159 163 // provides: style: {}
160 164 useRunStep(
161 165 'siteStyle',
162 166 () => {
163 - // We just need the site profile, which has this
164 167 if (!data.siteProfile?.title) return null;
165 168 return data;
166 169 },
167 170 async (params) => {
@@ -174,9 +177,8 @@
174 177 // provides: aiHeaders: [], aiBlogTitles: []
175 178 useRunStep(
176 179 'siteStrings',
177 180 () => {
178 - // We just need the site profile, which has this
179 181 if (!data.siteProfile?.title) return null;
180 182 return data;
181 183 },
182 184 async (params) => {
@@ -189,9 +191,8 @@
189 191 // provides: siteImages: []
190 192 useRunStep(
191 193 'siteImages',
192 194 () => {
193 - // We just need the site profile, which has this
194 195 if (!data.siteProfile?.title) return null;
195 196 return data;
196 197 },
197 198 async (params) => {
@@ -199,14 +200,27 @@
199 200 return await handleSiteImages(params);
200 201 },
201 202 );
202 203
204 + // needs: siteProfile
205 + // provides: launchDecisions: { navExtras, navButtonLabel }
206 + useRunStep(
207 + 'launchDecisions',
208 + () => {
209 + if (!data.siteProfile?.title) return null;
210 + return data;
211 + },
212 + async (params) => {
213 + checkIn({ stage: 'get_launch_decisions' });
214 + return await handleLaunchDecisions(params);
215 + },
216 + );
217 +
203 218 // needs: siteProfile, sitePlugins, siteStyle, siteImages, aiHeaders
204 219 // provides: home: { id, slug, patterns, siteStyle }
205 220 useRunStep(
206 221 'home',
207 222 () => {
208 - // Checking various data from calls above
209 223 const ok = [
210 224 data.siteProfile,
211 225 data.siteStyle,
212 226 data.siteImages,
@@ -220,14 +234,13 @@
220 234 return await handleHome(params);
221 235 },
222 236 );
223 237
224 - // siteProfile, sitePlugins, siteStyle, siteImages
238 + // needs: siteProfile, sitePlugins, siteStyle, siteImages
225 239 // provides: pages: [{ id, slug, name, patterns, siteStyle }]
226 240 useRunStep(
227 241 'pages',
228 242 () => {
229 - // Checking various data from calls above
230 243 const ok = [
231 244 data.siteProfile,
232 245 data.siteStyle,
233 246 data.siteImages,
@@ -254,14 +267,11 @@
254 267 checkIn({ stage: 'set_general', siteProfile, sitePlugins, siteStyle });
255 268
256 269 const { title } = siteProfile;
257 270 // translators: this is for a action log UI. Keep it short
258 - addStatusMessage(__('Adding admin configurations', 'extendify-local'));
259 - // update permalinks
271 + addStatusMessage(launchStrings().statusAdmin);
260 272 await updateOption('permalink_structure', '/%postname%/');
261 - // make sure consent is set
262 273 setUserGaveConsent(true);
263 - // Update title
264 274 if (title) await updateOption('blogname', title);
265 275 },
266 276 );
267 277
@@ -299,8 +309,9 @@
299 309 siteStyle,
300 310 aiBlogTitles,
301 311 siteImages,
302 312 designBuild,
313 + launchDecisions,
303 314 } = data;
304 315 // pages could be [] and pass here, that's ok
305 316 if (!home || !pages) return;
306 317 if (homeStretch.current) return;
@@ -306,10 +317,23 @@
306 317 if (homeStretch.current) return;
307 318 homeStretch.current = true;
308 319 (async () => {
309 320 const { objective, structure, category } = siteProfile;
321 + const builtHome = designBuild?.builtPages?.find((p) => p.slug === 'home');
322 + // Must match get-home.js's full-page test; if they drift, the home is
323 + // built one way and planted another.
324 + const isSinglePageDesign =
325 + structure === 'single-page' &&
326 + Boolean(builtHome?.fullPage && builtHome.patterns?.length) &&
327 + Boolean(designBuild?.pages?.length);
328 + const imageUrls = siteImageUrls(siteImages);
329 + const intendedPlugins = (sitePlugins ?? []).map(
330 + ({ wordpressSlug }) => wordpressSlug,
331 + );
310 332
311 - // Do they need an imprint page?
333 + // Guarantee plugins are active before the pattern imports below rely on them.
334 + await ensurePluginsActive(intendedPlugins);
335 +
312 336 const needsImprint = Array.isArray(showImprint)
313 337 ? showImprint.includes(wpLanguage ?? '') && category === 'Business'
314 338 : false;
315 339
@@ -318,9 +342,9 @@
318 342 let variation = siteStyle?.variation;
319 343 if (customFonts?.length) {
320 344 checkIn({ stage: 'install_fonts' });
321 345 // translators: this is for a action log UI. Keep it short
322 - addStatusMessage(__('Installing fonts locally', 'extendify-local'));
346 + addStatusMessage(launchStrings().statusFonts);
323 347 const installed = await installFontFamilies(customFonts).catch(
324 348 () => [],
325 349 );
326 350 variation = mergeFontsIntoVariation(siteStyle.variation, installed);
@@ -327,19 +351,15 @@
327 351 }
328 352
329 353 if (siteStyle?.vibe && siteStyle.vibe !== 'natural-1') {
330 354 // translators: vibe in this context is a noun - the feeling of their site design.
331 - addStatusMessage(__('Setting the website style', 'extendify-local'));
355 + addStatusMessage(launchStrings().statusSiteStyle);
332 356 checkIn({ stage: 'compute_vibe' });
333 - const vibeBlocks = await computeVibeAdjustedBlocks(
357 + const vibe = await computeVibeAdjustments(
334 358 siteStyle.vibe,
359 + variation,
335 360 ).catch(() => null);
336 - if (vibeBlocks) {
337 - variation = {
338 - ...variation,
339 - styles: { ...variation.styles, blocks: vibeBlocks },
340 - };
341 - }
361 + if (vibe) variation = { ...variation, ...vibe };
342 362 }
343 363
344 364 checkIn({ stage: 'set_vibe' });
345 365 await updateVariation(variation);
@@ -344,9 +364,9 @@
344 364 checkIn({ stage: 'set_vibe' });
345 365 await updateVariation(variation);
346 366
347 367 // navigation menu
348 - addStatusMessage(__('Working on the navigation', 'extendify-local'));
368 + addStatusMessage(launchStrings().statusNavigation);
349 369 const { id: headerNavId } = await createNavigation({
350 370 title: __('Header Navigation', 'extendify-local'),
351 371 slug: 'site-navigation',
352 372 });
@@ -352,12 +372,13 @@
352 372 });
353 373 let headerCode = updateNavAttributes(home.headerCode || '', {
354 374 ref: headerNavId,
355 375 });
376 + headerCode = injectNavExtras(headerCode, launchDecisions);
356 377 // remove the header navigation from the landing page
357 378 if (objective === 'landing-page') {
358 379 // translators: this is for a action log UI. Keep it short
359 - addStatusMessage(__('Perfecting a landing page', 'extendify-local'));
380 + addStatusMessage(launchStrings().statusLanding);
360 381 const social =
361 382 /<!--\s*wp:social-links\b[^>]*>.*?<!--\s*\/wp:social-links\s*-->/gis;
362 383 headerCode = headerCode
363 384 .replace(/<!--\s*wp:navigation\b[^>]*.*\/-->/gis, '')
@@ -362,23 +383,27 @@
362 383 headerCode = headerCode
363 384 .replace(/<!--\s*wp:navigation\b[^>]*.*\/-->/gis, '')
364 385 .replace(social, '');
365 386 }
366 - if (typeof siteProfile.phoneNumber === 'string') {
367 - headerCode = headerCode.replaceAll(
368 - // Hardcoded in the template
369 - '206-555-0100',
370 - siteProfile.phoneNumber ||
371 - // translators: Use a number that is appropriate for the locale. It does not need to be this exact number. This is a placeholder phone number. For example, in pt_BR you could use (11) 91234-5678.
372 - __('206-555-0100', 'extendify-local'),
373 - );
374 - }
387 + headerCode = headerCode.replaceAll(
388 + '206-555-0100',
389 + (typeof siteProfile.phoneNumber === 'string' &&
390 + siteProfile.phoneNumber) ||
391 + // translators: Use a number that is appropriate for the locale. It does not need to be this exact number. This is a placeholder phone number. For example, in pt_BR you could use (11) 91234-5678.
392 + __('206-555-0100', 'extendify-local'),
393 + );
375 394 checkIn({ stage: 'set_navigation' });
376 - await updateTemplatePart('extendable/header', headerCode);
377 395
378 396 // footer
379 397 let footerNavId = null;
380 398 let footerCode = home.footerCode || '';
399 + // The logo already carries the brand, so the site title would be redundant.
400 + if (designBuild?.hasExternalLogo && footerCode.includes('wp:site-logo')) {
401 + footerCode = footerCode.replace(
402 + /\s*<!--\s*wp:site-title[\s\S]*?\/-->/g,
403 + '',
404 + );
405 + }
381 406 if (needsImprint) {
382 407 const nav = await createNavigation({
383 408 title: __('Footer Navigation', 'extendify-local'),
384 409 slug: 'footer-navigation',
@@ -385,14 +410,15 @@
385 410 });
386 411 footerNavId = nav.id;
387 412 footerCode = updateNavAttributes(footerCode, { ref: footerNavId });
388 413 }
414 + footerCode = applySocialProfiles(footerCode, siteProfile.socialProfiles);
389 415 checkIn({ stage: 'set_footer' });
390 416 await updateTemplatePart('extendable/footer', footerCode);
391 417
392 418 // pages
393 419 // translators: this is for a action log UI. Keep it short
394 - addStatusMessage(__('Creating pages', 'extendify-local'));
420 + addStatusMessage(launchStrings().statusCreatingPages);
395 421 const pagesToCreate = getPagesToCreate(data);
396 422 const titlePattern = pages?.[0]?.patterns?.find((p) =>
397 423 p.patternTypes?.includes('page-title'),
398 424 );
@@ -401,9 +427,9 @@
401 427 await updatePageTitlePattern(titlePattern.code);
402 428 }
403 429
404 430 const activePlugins = await getActivePlugins();
405 - // This lets us keep plugin pages in th enav but skip making the page
431 + // Keep plugin pages in the nav but skip creating the page itself.
406 432 const reservedSlugs = new Set(
407 433 PLUGIN_OWNED_PAGES.filter(
408 434 ({ plugin }) =>
409 435 sitePlugins.some((p) => p.wordpressSlug === plugin) ||
@@ -427,25 +453,43 @@
427 453 }
428 454 checkIn({ stage: 'generate_page_content' });
429 455 const customPages = await generatePageContent(pagesReplaced, data);
430 456
431 - const stickyNav =
432 - structure === 'single-page' && objective !== 'landing-page';
433 - const createdPagesWP = await createWpPages(customPages, { stickyNav });
457 + // Update heroDescription to the actual AI-rewritten hero content
458 + const homePage = customPages.find((p) => p.slug === 'home');
459 + const heroPattern = homePage?.patterns?.find((p) =>
460 + p.patternTypes?.includes('hero-header'),
461 + );
462 + const pMatch = heroPattern?.code?.match(/<p[^>]*>([\s\S]*?)<\/p>/);
463 + const heroDesc = pMatch?.[1]?.replace(/<[^>]+>/g, '').trim();
464 + const heroDescription = heroDesc || data.heroDescription;
465 + setData('heroDescription', heroDescription);
466 + if (heroDescription) {
467 + await updateOption('extendify_hero_description', heroDescription).catch(
468 + () => null,
469 + );
470 + }
471 +
472 + const createdPagesWP = await createWpPages(customPages, {
473 + skipSectionIds: isSinglePageDesign,
474 + });
434 475 // Aux pages
435 - const hasBlogPattern = home?.patterns?.some((pattern) =>
476 + const blogPattern = home?.patterns?.find((pattern) =>
436 477 pattern.patternTypes.includes('blog-section'),
437 478 );
438 - if (objective === 'blog' || hasBlogPattern) {
479 + if (siteProfile.blog || blogPattern) {
439 480 checkIn({ stage: 'create_blog_sample_data' });
440 481 // translators: this is for a action log UI. Keep it short
441 - addStatusMessage(__('Creating blog sample data', 'extendify-local'));
442 - await createBlogSampleData({ aiBlogTitles }, siteImages);
482 + addStatusMessage(launchStrings().statusBlog);
483 + await createBlogSampleData(
484 + { aiBlogTitles },
485 + imageUrls,
486 + blogPattern?.blogImages,
487 + );
443 488 }
444 - // If we have site images then set up the hello world image
445 - if (siteImages?.length) {
489 + if (imageUrls.length) {
446 490 checkIn({ stage: 'set_hello_world_image' });
447 - await setHelloWorldFeaturedImage(siteImages);
491 + await setHelloWorldFeaturedImage(imageUrls);
448 492 }
449 493
450 494 let imprint = {};
451 495 if (needsImprint) {
@@ -457,9 +501,9 @@
457 501 if (alreadyActive(activePlugins, 'woocommerce')) {
458 502 checkIn({ stage: 'import_woocommerce_products' });
459 503 addStatusMessage(
460 504 // translators: this is for a action log UI. Keep it short
461 - __('Setting up your online store', 'extendify-local'),
505 + launchStrings().statusStore,
462 506 );
463 507 await apiFetchWithTimeout({
464 508 path: '/extendify/v1/auto-launch/import-woocommerce',
465 509 }).catch(() => null);
@@ -474,18 +518,36 @@
474 518 link: `${homeUrl}/events`,
475 519 });
476 520 }
477 521
522 + // The design's page list omits the posts page like it omits shop, so the
523 + // nav needs it here — but it's ours to create, not a PLUGIN_OWNED_PAGES.
524 + pluginPages.push(...createdPagesWP.filter(isBlogPage));
525 +
478 526 // Adding pages to the nav
479 527 checkIn({ stage: 'set_page_links' });
480 - const pagesWithLinksUpdated =
481 - structure === 'single-page'
482 - ? await updateSinglePageLinksToSections(createdPagesWP, customPages, {
483 - objective,
484 - activePlugins,
485 - landingPageCTALink: siteProfile.landingPageCTALink,
486 - })
487 - : await updateButtonLinks(createdPagesWP, pluginPages);
528 + let linksResult = { wpPages: createdPagesWP, headerCode };
529 + if (!isSinglePageDesign) {
530 + linksResult =
531 + structure === 'single-page'
532 + ? await updateSinglePageLinksToSections(
533 + createdPagesWP,
534 + customPages,
535 + {
536 + objective,
537 + activePlugins,
538 + landingPageCTALink: siteProfile.landingPageCTALink,
539 + },
540 + headerCode,
541 + )
542 + : await updateButtonLinks(createdPagesWP, pluginPages, headerCode);
543 + }
544 + const pagesWithLinksUpdated = linksResult.wpPages;
545 + headerCode = linksResult.headerCode;
546 + if (alreadyActive(activePlugins, 'woocommerce')) {
547 + headerCode = injectWooCommerceIcons(headerCode);
548 + }
549 + await updateTemplatePart('extendable/header', headerCode);
488 550 const footerNavPages = [];
489 551 if (footerNavId && imprint?.title) {
490 552 const { originalSlug, title } = imprint;
491 553 footerNavPages.push({
@@ -498,9 +560,20 @@
498 560
499 561 checkIn({ stage: 'set_navigation_links' });
500 562 if (objective !== 'landing-page') {
501 563 const orderedSlugs = designBuild?.pages?.map((p) => p.slug) ?? [];
502 - if (structure === 'single-page') {
564 + // The tag only exists where the sections came back matched 1:1 to our list;
565 + // read it off what was planted, so menu and section ids can't disagree.
566 + const designOwnsNav =
567 + isSinglePageDesign ||
568 + Boolean(homePage?.patterns?.some(({ navSlug }) => navSlug));
569 + if (designOwnsNav) {
570 + await addSectionLinksFromDesign(
571 + headerNavId,
572 + designBuild.pages,
573 + pluginPages,
574 + );
575 + } else if (structure === 'single-page') {
503 576 await addSectionLinksToNav(
504 577 headerNavId,
505 578 home?.patterns,
506 579 pluginPages,
@@ -532,24 +605,30 @@
532 605 await prefetchAssistData();
533 606 checkIn({ stage: 'final_steps' });
534 607 await setThemeRenderingMode('template-locked');
535 608 await postLaunchFunctions();
609 + if (imageUrls.length) {
610 + await storeSiteImages(siteImages).catch(() => null);
611 + }
536 612 // translators: this is for a action log UI. Keep it short
537 - addStatusMessage(__('All done!', 'extendify-local'));
538 - await checkIn({ stage: 'finished', siteProfile, sitePlugins, siteStyle });
613 + addStatusMessage(launchStrings().statusDone);
614 + await Promise.all([
615 + reportInactivePlugins(intendedPlugins).catch(() => null),
616 + reportFailedDependencies(pagesReplaced),
617 + checkIn({ stage: 'finished', siteProfile, sitePlugins, siteStyle }),
618 + ]);
539 619 setWarnOnReload(false);
540 620 setDone(true);
541 621 })().catch((error) => {
542 622 console.error(error);
623 + digest({
624 + error,
625 + details: { source: 'auto-launch', caller: 'create-site' },
626 + });
543 627 // if we error here we can try again by resetting the home stretch and stalling again to refetch data
544 628 homeStretch.current = false;
545 629 needToStall(true);
546 - setErrorMessage(
547 - __(
548 - 'Something went wrong during the final steps. We will try again but you may need to refresh the page.',
549 - 'extendify-local',
550 - ),
551 - );
630 + setErrorMessage(launchStrings().errorFinalSteps);
552 631 });
553 632 }, [data, needToStall, setUserGaveConsent]);
554 633
555 634 return { done };
@@ -572,12 +651,8 @@
572 651
573 652 useEffect(() => {
574 653 if (!error || needToStall()) return;
575 654 console.error(error);
576 - setErrorMessage(
577 - __(
578 - 'Having some trouble with this step. Trying again...',
579 - 'extendify-local',
580 - ),
581 - );
655 + digest({ error, details: { source: 'auto-launch', caller: 'run-step' } });
656 + setErrorMessage(launchStrings().errorStep);
582 657 }, [error, setErrorMessage, needToStall]);
583 658 };