PluginProbe
Extendify / trunk
Extendify vtrunk
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 0.6.0 All 127 releases
← All changes | src/AutoLaunch/hooks/useCreateSite.js +164 -89 3.0.4 → trunk 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 };