@wix/zero-config-implementation 1.86.0 → 1.87.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "registry": "https://registry.npmjs.org/",
5
5
  "access": "public"
6
6
  },
7
- "version": "1.86.0",
7
+ "version": "1.87.0",
8
8
  "description": "Core library for extracting component manifests from JS and CSS files",
9
9
  "type": "module",
10
10
  "main": "dist/index.js",
@@ -39,8 +39,32 @@
39
39
  }
40
40
  },
41
41
  "dependencies": {
42
- "@wix/builder-services-wrapper": "^1.56.0",
43
- "@wix/react-component-schema": "1.8.0"
42
+ "@wix/react-component-schema": "1.8.0",
43
+ "@wix/seo-service": "^1.9.0",
44
+ "@wix/services-manager": "^1.0.7",
45
+ "@wix/services-manager-react": "^1.0.8",
46
+ "@wix/site-service-configuration": "^1.0.1",
47
+ "@wix/site-service-device-info": "^1.0.2",
48
+ "@wix/site-service-editor-context": "^1.0.1",
49
+ "@wix/site-service-experiments": "^1.0.3",
50
+ "@wix/site-service-locale": "^1.0.2",
51
+ "@wix/site-service-pages": "^1.0.2",
52
+ "@wix/site-service-provide-css": "^1.0.1",
53
+ "@wix/site-service-rendering-context": "^1.0.1",
54
+ "@wix/site-service-url": "^1.0.1",
55
+ "@wix/viewer-service-anchors": "^1.0.40",
56
+ "@wix/viewer-service-consent-policy": "^1.0.100",
57
+ "@wix/viewer-service-environment": "^1.0.47",
58
+ "@wix/viewer-service-link-utils": "^1.0.62",
59
+ "@wix/viewer-service-named-signals": "^1.0.16",
60
+ "@wix/viewer-service-pages": "^1.0.9",
61
+ "@wix/viewer-service-provide-css": "^1.0.31",
62
+ "@wix/viewer-service-renderer-configuration": "^1.0.7",
63
+ "@wix/viewer-service-sdk-state": "^1.0.28",
64
+ "@wix/viewer-service-site-scroll-blocker": "^1.0.63",
65
+ "@wix/viewer-service-topology": "^1.0.26",
66
+ "@wix/viewer-service-translations": "^1.0.15",
67
+ "@wix/viewer-service-url": "^1.0.69"
44
68
  },
45
69
  "devDependencies": {
46
70
  "@faker-js/faker": "^10.2.0",
@@ -82,5 +106,5 @@
82
106
  ]
83
107
  }
84
108
  },
85
- "falconPackageHash": "88fd007d78421c0861fdf823019f98813fd8a82582461e104c5ee2ef"
109
+ "falconPackageHash": "1db79cb96c2d3ad1aa00f8c608385635757b7899b0f11f7a74f228e4"
86
110
  }
@@ -0,0 +1,149 @@
1
+ import { createServicesManager, createServicesMap } from '@wix/services-manager'
2
+ import type { ServiceDefinition } from '@wix/services-manager/types'
3
+ import React from 'react'
4
+ import { renderToStaticMarkup } from 'react-dom/server'
5
+ import { describe, expect, it, vi } from 'vitest'
6
+ import { createAutoStubFallbackManager } from './service-stub'
7
+
8
+ // Definitions are plain strings at runtime, so a cast string is a faithful stand-in.
9
+ function defineTestService(serviceId: string): ServiceDefinition<never, never> {
10
+ return serviceId as unknown as ServiceDefinition<never, never>
11
+ }
12
+
13
+ const UnregisteredDefinition = defineTestService('@wix/viewer-service-topology')
14
+
15
+ // biome-ignore lint/suspicious/noExplicitAny: stubs are intentionally untyped at the call site
16
+ function stubFor(serviceId: string): any {
17
+ return createAutoStubFallbackManager().getService(defineTestService(serviceId))
18
+ }
19
+
20
+ describe('createAutoStubFallbackManager', () => {
21
+ it('returns a referentially stable stub across repeated lookups', () => {
22
+ const fallbackManager = createAutoStubFallbackManager()
23
+
24
+ expect(fallbackManager.getService(UnregisteredDefinition)).toBe(fallbackManager.getService(UnregisteredDefinition))
25
+ })
26
+
27
+ it('notifies once per distinct service id', () => {
28
+ const onServiceStubbed = vi.fn()
29
+ const fallbackManager = createAutoStubFallbackManager({ onServiceStubbed })
30
+
31
+ fallbackManager.getService(UnregisteredDefinition)
32
+ fallbackManager.getService(UnregisteredDefinition)
33
+ fallbackManager.getService(defineTestService('@wix/seo-service'))
34
+
35
+ expect(onServiceStubbed.mock.calls).toEqual([['@wix/viewer-service-topology'], ['@wix/seo-service']])
36
+ })
37
+ })
38
+
39
+ describe('service stub value semantics', () => {
40
+ it('resolves nested reads and curried calls to memoized stubs', () => {
41
+ const pagesStub = stubFor('@wix/site-service-pages')
42
+ expect(pagesStub.currentPage.title).toBe(pagesStub.currentPage.title)
43
+
44
+ const translationsStub = stubFor('@wix/viewer-service-translations')
45
+ expect(translationsStub.translate('app')('greeting')).toBe(translationsStub.translate('app')('greeting'))
46
+ })
47
+
48
+ it('keeps well-known symbol handlers referentially stable', () => {
49
+ const stub = stubFor('@wix/viewer-service-topology')
50
+
51
+ expect(stub.toString).toBe(stub.toString)
52
+ expect(stub[Symbol.iterator]).toBe(stub[Symbol.iterator])
53
+ })
54
+
55
+ it('agrees between `in` and reads for symbols', () => {
56
+ const stub = stubFor('@wix/viewer-service-topology')
57
+ const unhandledSymbol = Symbol.for('some-library-symbol')
58
+
59
+ // Reporting a symbol as present while reading undefined would break
60
+ // `if (sym in service) service[sym]()`.
61
+ expect(unhandledSymbol in stub).toBe(false)
62
+ expect(stub[unhandledSymbol]).toBeUndefined()
63
+
64
+ expect(Symbol.iterator in stub).toBe(true)
65
+ expect(stub[Symbol.iterator]).toBeDefined()
66
+ })
67
+
68
+ it('is not thenable, so awaiting it cannot hang', async () => {
69
+ const stub = stubFor('@wix/seo-service')
70
+
71
+ expect(stub.then).toBeUndefined()
72
+ expect('then' in stub).toBe(false)
73
+ await expect(Promise.resolve(stub)).resolves.toBe(stub)
74
+ })
75
+
76
+ it('terminates on spread and key enumeration instead of recursing', () => {
77
+ const stub = stubFor('@wix/viewer-service-sdk-state')
78
+
79
+ expect(Object.keys(stub)).toEqual([])
80
+ expect({ ...stub }).toEqual({})
81
+ })
82
+
83
+ it('reads as an empty string and iterates as empty', () => {
84
+ const stub = stubFor('@wix/viewer-service-topology')
85
+
86
+ expect(`${stub}`).toBe('')
87
+ expect([...stub]).toEqual([])
88
+ })
89
+
90
+ it('does not crash React when handed to JSX as a child', () => {
91
+ // Unconverted, so React sees the Proxy itself — and throws on plain-object children.
92
+ const reactWarning = vi.spyOn(console, 'error').mockImplementation(() => {})
93
+ const stub = stubFor('@wix/viewer-service-topology')
94
+
95
+ expect(() => renderToStaticMarkup(React.createElement('span', null, stub))).not.toThrow()
96
+
97
+ reactWarning.mockRestore()
98
+ })
99
+ })
100
+
101
+ describe('used as a services-manager fallback', () => {
102
+ const RegisteredDefinition = defineTestService('@wix/registered-test-service')
103
+
104
+ function createManagerWithFallback(onServiceStubbed?: (serviceId: string) => void) {
105
+ return createServicesManager(
106
+ createServicesMap([
107
+ {
108
+ definition: RegisteredDefinition,
109
+ impl: (() => ({ realValue: 'from-real-service' })) as never,
110
+ config: {} as never,
111
+ },
112
+ ]),
113
+ createAutoStubFallbackManager({ onServiceStubbed }),
114
+ )
115
+ }
116
+
117
+ it('keeps extended bindings real and still stubs the rest', () => {
118
+ const ExtendedDefinition = defineTestService('@wix/extended-test-service')
119
+ const extended = createAutoStubFallbackManager().extend(
120
+ createServicesMap([
121
+ {
122
+ definition: ExtendedDefinition,
123
+ impl: (() => ({ realValue: 'from-extension' })) as never,
124
+ config: {} as never,
125
+ },
126
+ ]),
127
+ )
128
+
129
+ expect(extended.getService(ExtendedDefinition)).toEqual({ realValue: 'from-extension' })
130
+ expect(() => extended.getService(UnregisteredDefinition)).not.toThrow()
131
+ })
132
+
133
+ it('leaves registered services untouched', () => {
134
+ const servicesManager = createManagerWithFallback()
135
+
136
+ expect(servicesManager.getService(RegisteredDefinition)).toEqual({ realValue: 'from-real-service' })
137
+ })
138
+
139
+ it('substitutes a stub where an unregistered service would otherwise throw', () => {
140
+ const onServiceStubbed = vi.fn()
141
+
142
+ expect(() => createServicesManager(createServicesMap([])).getService(UnregisteredDefinition)).toThrow(
143
+ /is not provided/,
144
+ )
145
+
146
+ expect(() => createManagerWithFallback(onServiceStubbed).getService(UnregisteredDefinition)).not.toThrow()
147
+ expect(onServiceStubbed).toHaveBeenCalledWith('@wix/viewer-service-topology')
148
+ })
149
+ })
@@ -0,0 +1,126 @@
1
+ import { createServicesManager } from '@wix/services-manager'
2
+ import type { ServicesManager, ServicesRegistrar } from '@wix/services-manager/types'
3
+
4
+ /** Definitions are plain strings at runtime, so one doubles as both registry key and service id. */
5
+ type ServiceDefinitionLike = { toString(): string }
6
+
7
+ /** A thenable stub would hang `await`; a stub with `$$typeof` would be read as a React element. */
8
+ const NEVER_STUBBED_PROPERTIES: ReadonlySet<PropertyKey> = new Set([
9
+ 'then',
10
+ 'catch',
11
+ 'finally',
12
+ '$$typeof',
13
+ 'toJSON',
14
+ 'nodeType',
15
+ ])
16
+
17
+ const HANDLED_SYMBOLS: ReadonlySet<symbol> = new Set([Symbol.toPrimitive, Symbol.toStringTag, Symbol.iterator])
18
+
19
+ // Shared, so `stub.toString === stub.toString` holds.
20
+ const readAsEmptyString = (): string => ''
21
+ function* iterateAsEmpty(): Generator<never> {}
22
+
23
+ // Not `Symbol.for`, whose key is global and could collide with another library's.
24
+ const CALL_RESULT_KEY = Symbol('call-result')
25
+
26
+ /**
27
+ * Inert but still truthy — a component branching on a stubbed flag takes the truthy path, which
28
+ * is why stubbed services are reported to the caller.
29
+ */
30
+ function createServiceStub(servicePath: string): unknown {
31
+ const childStubs = new Map<PropertyKey, unknown>()
32
+
33
+ // Arrow function: callable, and owns no non-configurable props that would make the
34
+ // `ownKeys`/`getOwnPropertyDescriptor` traps below violate the Proxy invariants.
35
+ const callableTarget = (): void => {}
36
+
37
+ return new Proxy(callableTarget, {
38
+ get(_target, property) {
39
+ if (NEVER_STUBBED_PROPERTIES.has(property)) {
40
+ return undefined
41
+ }
42
+ if (property === Symbol.toPrimitive || property === 'valueOf' || property === 'toString') {
43
+ return readAsEmptyString
44
+ }
45
+ if (property === Symbol.toStringTag) {
46
+ return 'WixServiceStub'
47
+ }
48
+ if (property === Symbol.iterator) {
49
+ return iterateAsEmpty
50
+ }
51
+ if (typeof property === 'symbol') {
52
+ return undefined
53
+ }
54
+
55
+ const existingChildStub = childStubs.get(property)
56
+ if (existingChildStub !== undefined) {
57
+ return existingChildStub
58
+ }
59
+
60
+ const childStub = createServiceStub(`${servicePath}.${property}`)
61
+ childStubs.set(property, childStub)
62
+ return childStub
63
+ },
64
+
65
+ apply() {
66
+ const existingCallResult = childStubs.get(CALL_RESULT_KEY)
67
+ if (existingCallResult !== undefined) {
68
+ return existingCallResult
69
+ }
70
+
71
+ const callResult = createServiceStub(`${servicePath}()`)
72
+ childStubs.set(CALL_RESULT_KEY, callResult)
73
+ return callResult
74
+ },
75
+
76
+ // Mirrors `get`, so `in` never reports a property that reads back as undefined.
77
+ has: (_target, property) => {
78
+ if (NEVER_STUBBED_PROPERTIES.has(property)) {
79
+ return false
80
+ }
81
+ return typeof property === 'symbol' ? HANDLED_SYMBOLS.has(property) : true
82
+ },
83
+ set: () => true,
84
+ // No own keys, so spreading terminates instead of recursing forever.
85
+ ownKeys: () => [],
86
+ getOwnPropertyDescriptor: () => undefined,
87
+ })
88
+ }
89
+
90
+ export interface AutoStubFallbackOptions {
91
+ /** Invoked once per distinct service id. */
92
+ onServiceStubbed?: (serviceId: string) => void
93
+ }
94
+
95
+ /**
96
+ * `createServicesManager` delegates to its `parentServicesManager` for unbound definitions, so
97
+ * passing this as the parent turns a "Service X is not provided" throw into an inert stub.
98
+ */
99
+ export function createAutoStubFallbackManager(options: AutoStubFallbackOptions = {}): ServicesManager {
100
+ const stubsByServiceId = new Map<string, unknown>()
101
+
102
+ const getService = (definition: ServiceDefinitionLike): never => {
103
+ const serviceId = definition.toString()
104
+
105
+ const existingStub = stubsByServiceId.get(serviceId)
106
+ if (existingStub !== undefined) {
107
+ return existingStub as never
108
+ }
109
+
110
+ const stub = createServiceStub(serviceId)
111
+ stubsByServiceId.set(serviceId, stub)
112
+ options.onServiceStubbed?.(serviceId)
113
+ return stub as never
114
+ }
115
+
116
+ const fallbackManager: ServicesManager = {
117
+ getService: getService as ServicesManager['getService'],
118
+ hasService: () => true,
119
+ addService: () => {},
120
+ addServices: () => {},
121
+ dispose: () => {},
122
+ extend: (servicesMap: ServicesRegistrar) => createServicesManager(servicesMap, fallbackManager),
123
+ }
124
+
125
+ return fallbackManager
126
+ }
@@ -0,0 +1,63 @@
1
+ import { useService } from '@wix/services-manager-react'
2
+ import type { ServiceDefinition } from '@wix/services-manager/types'
3
+ import { TranslationsDefinition } from '@wix/viewer-service-translations/definition'
4
+ import React, { type ComponentType } from 'react'
5
+ import { renderToStaticMarkup } from 'react-dom/server'
6
+ import { describe, expect, it, vi } from 'vitest'
7
+ import { wrapWithWixServices } from './wrapper'
8
+
9
+ const UnregisteredDefinition = '@wix/example-unregistered-analytics' as unknown as ServiceDefinition<
10
+ { trackingId: string },
11
+ never
12
+ >
13
+
14
+ function renderThroughWrapper(Component: ComponentType<unknown>): string {
15
+ return renderToStaticMarkup(React.createElement(wrapWithWixServices(Component), {}))
16
+ }
17
+
18
+ describe('wrapWithWixServices', () => {
19
+ it('resolves a registered service to its real implementation, stubbing nothing', () => {
20
+ const warn = vi.spyOn(console, 'warn').mockImplementation(() => {})
21
+
22
+ const TranslatingComponent: ComponentType<unknown> = () => {
23
+ const translations = useService(TranslationsDefinition)
24
+ return React.createElement('span', null, translations.translate('app')('greeting') ?? 'fallback-text')
25
+ }
26
+
27
+ // The real service echoes the key back; a stub would have produced an empty string.
28
+ expect(renderThroughWrapper(TranslatingComponent)).toContain('<span>greeting</span>')
29
+ expect(warn).not.toHaveBeenCalled()
30
+
31
+ warn.mockRestore()
32
+ })
33
+
34
+ it('warns per extraction', () => {
35
+ const warn = vi.spyOn(console, 'warn').mockImplementation(() => {})
36
+
37
+ const AnalyticsComponent: ComponentType<unknown> = () => {
38
+ useService(UnregisteredDefinition)
39
+ return null
40
+ }
41
+
42
+ // A module-level dedupe would leave every component after the first silently degraded.
43
+ renderThroughWrapper(AnalyticsComponent)
44
+ renderThroughWrapper(AnalyticsComponent)
45
+
46
+ expect(warn).toHaveBeenCalledTimes(2)
47
+
48
+ warn.mockRestore()
49
+ })
50
+
51
+ it('renders a component that reads an unregistered service instead of throwing', () => {
52
+ const warn = vi.spyOn(console, 'warn').mockImplementation(() => {})
53
+
54
+ const AnalyticsComponent: ComponentType<unknown> = () => {
55
+ const analytics = useService(UnregisteredDefinition)
56
+ return React.createElement('div', { 'data-tracking-id': String(analytics.trackingId) }, 'rendered')
57
+ }
58
+
59
+ expect(renderThroughWrapper(AnalyticsComponent)).toContain('<div data-tracking-id="">rendered</div>')
60
+
61
+ warn.mockRestore()
62
+ })
63
+ })
@@ -0,0 +1,204 @@
1
+ import { SeoServiceDefinition } from '@wix/seo-service/definition'
2
+ import { createSeoService } from '@wix/seo-service/implementations'
3
+ import { createServicesManager, createServicesMap } from '@wix/services-manager'
4
+ import { ServicesManagerProvider } from '@wix/services-manager-react'
5
+ import {
6
+ ProvideComponentServiceDefinition,
7
+ ProvideComponentServiceFactory,
8
+ } from '@wix/services-manager-react/core-services/provide-component'
9
+ import type { ServiceBinding } from '@wix/services-manager/types'
10
+ import { ConfigurationDefinition } from '@wix/site-service-configuration/definition'
11
+ import { ConfigurationService } from '@wix/site-service-configuration/implementations'
12
+ import { DeviceInfoDefinition } from '@wix/site-service-device-info/definition'
13
+ import { DeviceInfoService } from '@wix/site-service-device-info/implementations'
14
+ import { EditorContextDefinition } from '@wix/site-service-editor-context/definition'
15
+ import { EditorContextService } from '@wix/site-service-editor-context/implementations'
16
+ import { ExperimentsDefinition } from '@wix/site-service-experiments/definition'
17
+ import { ExperimentsService } from '@wix/site-service-experiments/implementations'
18
+ import { LocaleDefinition } from '@wix/site-service-locale/definition'
19
+ import { LocaleService } from '@wix/site-service-locale/implementations'
20
+ import type { IPagesServiceConfig, Page } from '@wix/site-service-pages/definition'
21
+ import { PagesDefinition } from '@wix/site-service-pages/definition'
22
+ import { PagesService } from '@wix/site-service-pages/implementations'
23
+ import { ProvideCssDefinition as SiteProvideCssDefinition } from '@wix/site-service-provide-css/definition'
24
+ import { ProvideCssService as SiteProvideCssService } from '@wix/site-service-provide-css/implementations'
25
+ import { RenderingContextDefinition } from '@wix/site-service-rendering-context/definition'
26
+ import { RenderingContextService } from '@wix/site-service-rendering-context/implementations'
27
+ import { UrlDefinition as SiteUrlDefinition } from '@wix/site-service-url/definition'
28
+ import { UrlService as SiteUrlService } from '@wix/site-service-url/implementations'
29
+ import { AnchorsDefinition } from '@wix/viewer-service-anchors/definition'
30
+ import { AnchorsService } from '@wix/viewer-service-anchors/implementations'
31
+ import { ConsentPolicyDefinition } from '@wix/viewer-service-consent-policy/definition'
32
+ import { ConsentPolicyService } from '@wix/viewer-service-consent-policy/implementations'
33
+ import { EnvironmentDefinition } from '@wix/viewer-service-environment/definition'
34
+ import { EnvironmentService } from '@wix/viewer-service-environment/implementations'
35
+ import { LinkUtilsDefinition } from '@wix/viewer-service-link-utils/definition'
36
+ import { LinkUtilsService } from '@wix/viewer-service-link-utils/implementations'
37
+ import { NamedSignalsDefinition } from '@wix/viewer-service-named-signals/definition'
38
+ import { NamedSignalsService } from '@wix/viewer-service-named-signals/implementations'
39
+ import type { IPagesServiceConfig as IViewerPagesServiceConfig } from '@wix/viewer-service-pages/definition'
40
+ import { PagesDefinition as ViewerPagesDefinition } from '@wix/viewer-service-pages/definition'
41
+ import { PagesService as ViewerPagesService } from '@wix/viewer-service-pages/implementations'
42
+ import { ProvideCssDefinition } from '@wix/viewer-service-provide-css/definition'
43
+ import { ProvideCssService } from '@wix/viewer-service-provide-css/implementations'
44
+ import { RendererConfigurationDefinition } from '@wix/viewer-service-renderer-configuration/definition'
45
+ import { RendererConfigurationService } from '@wix/viewer-service-renderer-configuration/implementations'
46
+ import { SdkStateDefinition } from '@wix/viewer-service-sdk-state/definition'
47
+ import { SdkStateService } from '@wix/viewer-service-sdk-state/implementations'
48
+ import { SiteScrollBlockerDefinition } from '@wix/viewer-service-site-scroll-blocker/definition'
49
+ import { SiteScrollBlockerService } from '@wix/viewer-service-site-scroll-blocker/implementations'
50
+ import type { Topology } from '@wix/viewer-service-topology/definition'
51
+ import { TopologyDefinition } from '@wix/viewer-service-topology/definition'
52
+ import { TopologyService } from '@wix/viewer-service-topology/implementations'
53
+ import { TranslationsDefinition } from '@wix/viewer-service-translations/definition'
54
+ import { TranslationsService } from '@wix/viewer-service-translations/implementations'
55
+ import { UrlDefinition } from '@wix/viewer-service-url/definition'
56
+ import { UrlService } from '@wix/viewer-service-url/implementations'
57
+ import React, { type ComponentType, type ReactNode } from 'react'
58
+
59
+ import { createAutoStubFallbackManager } from './service-stub'
60
+
61
+ const PLACEHOLDER_SITE_URL = 'https://wix.com'
62
+ const PLACEHOLDER_EXPERIMENTS = {
63
+ 'specs.thunderbolt.isClassNameToRootEnabled': true,
64
+ }
65
+ const PLACEHOLDER_HOME_PAGE: Page = {
66
+ id: 'home',
67
+ title: 'Home',
68
+ path: '/',
69
+ popup: false,
70
+ }
71
+ const PLACEHOLDER_CURRENT_PAGE: Page = {
72
+ id: 'audio',
73
+ title: 'Audio',
74
+ path: '/shop/audio',
75
+ parentPageId: 'shop',
76
+ popup: false,
77
+ }
78
+ const PLACEHOLDER_PAGES_CONFIG: IPagesServiceConfig = {
79
+ pages: {
80
+ home: { title: 'Home', path: '/', popup: false },
81
+ shop: { title: 'Shop', path: '/shop', parentPageId: 'home', popup: false },
82
+ audio: { title: 'Audio', path: '/shop/audio', parentPageId: 'shop', popup: false },
83
+ },
84
+ mainPage: PLACEHOLDER_HOME_PAGE,
85
+ currentPage: PLACEHOLDER_CURRENT_PAGE,
86
+ }
87
+
88
+ /** Derived, so the deprecated pages service and its successor describe the same site. */
89
+ const PLACEHOLDER_VIEWER_PAGES_CONFIG: IViewerPagesServiceConfig = {
90
+ pages: Object.fromEntries(
91
+ Object.entries(PLACEHOLDER_PAGES_CONFIG.pages).map(([pageId, { title, path, parentPageId }]) => [
92
+ pageId,
93
+ { title, path, parentPageId },
94
+ ]),
95
+ ),
96
+ currentPageId: PLACEHOLDER_PAGES_CONFIG.currentPage.id,
97
+ mainPageId: PLACEHOLDER_PAGES_CONFIG.mainPage.id,
98
+ }
99
+
100
+ const PLACEHOLDER_TOPOLOGY: Topology = {
101
+ mediaRootUrl: 'https://static.wixstatic.com/',
102
+ staticMediaUrl: 'https://static.wixstatic.com/media',
103
+ fileRepoUrl: 'https://static.wixstatic.com/ugd',
104
+ staticHTMLComponentUrl: 'https://static.wixstatic.com/html',
105
+ scriptsUrl: 'https://static.parastorage.com/',
106
+ }
107
+
108
+ // biome-ignore lint/suspicious/noExplicitAny: service bindings are heterogeneous by construction
109
+ type AnyServiceBinding = ServiceBinding<any, any>
110
+
111
+ /** Anything omitted here resolves through {@link createAutoStubFallbackManager} instead. */
112
+ function createServiceBindings(): AnyServiceBinding[] {
113
+ return [
114
+ {
115
+ definition: EnvironmentDefinition,
116
+ impl: EnvironmentService,
117
+ config: { experiments: PLACEHOLDER_EXPERIMENTS },
118
+ },
119
+ { definition: TranslationsDefinition, impl: TranslationsService, config: { translations: {} } },
120
+ {
121
+ definition: UrlDefinition,
122
+ impl: UrlService,
123
+ config: { externalBaseUrl: PLACEHOLDER_SITE_URL, requestUrl: PLACEHOLDER_SITE_URL },
124
+ },
125
+ { definition: LinkUtilsDefinition, impl: LinkUtilsService, config: {} },
126
+ { definition: AnchorsDefinition, impl: AnchorsService, config: {} },
127
+ { definition: SiteScrollBlockerDefinition, impl: SiteScrollBlockerService, config: {} },
128
+ { definition: ProvideCssDefinition, impl: ProvideCssService, config: {} },
129
+ { definition: RenderingContextDefinition, impl: RenderingContextService, config: {} },
130
+ { definition: ExperimentsDefinition, impl: ExperimentsService, config: { experiments: PLACEHOLDER_EXPERIMENTS } },
131
+ {
132
+ definition: ConfigurationDefinition,
133
+ impl: ConfigurationService,
134
+ config: { qaMode: false, trackClicksAnalytics: false, sandboxInHTMLComp: false },
135
+ },
136
+ { definition: SiteProvideCssDefinition, impl: SiteProvideCssService, config: {} },
137
+ { definition: DeviceInfoDefinition, impl: DeviceInfoService, config: {} },
138
+ { definition: EditorContextDefinition, impl: EditorContextService, config: { previewMode: false } },
139
+ {
140
+ definition: SiteUrlDefinition,
141
+ impl: SiteUrlService,
142
+ config: {
143
+ currentUrl: PLACEHOLDER_SITE_URL,
144
+ siteUrl: PLACEHOLDER_SITE_URL,
145
+ pages: [],
146
+ pageIdToPrefix: {},
147
+ },
148
+ },
149
+ { definition: PagesDefinition, impl: PagesService, config: PLACEHOLDER_PAGES_CONFIG },
150
+ { definition: ViewerPagesDefinition, impl: ViewerPagesService, config: PLACEHOLDER_VIEWER_PAGES_CONFIG },
151
+ { definition: LocaleDefinition, impl: LocaleService, config: { language: 'en', direction: 'ltr' } },
152
+ { definition: TopologyDefinition, impl: TopologyService, config: { topology: PLACEHOLDER_TOPOLOGY } },
153
+ {
154
+ definition: RendererConfigurationDefinition,
155
+ impl: RendererConfigurationService,
156
+ config: { isHipaaCompliant: false, currency: 'USD' },
157
+ },
158
+ { definition: SdkStateDefinition, impl: SdkStateService, config: {} },
159
+ { definition: NamedSignalsDefinition, impl: NamedSignalsService, config: {} },
160
+ { definition: ConsentPolicyDefinition, impl: ConsentPolicyService, config: { siteConsentPolicy: undefined } },
161
+ {
162
+ definition: SeoServiceDefinition,
163
+ impl: createSeoService,
164
+ config: { seoApi: { setVeloSeoTags: async () => {}, resetVeloSeoTags: async () => {} } },
165
+ },
166
+ { definition: ProvideComponentServiceDefinition, impl: ProvideComponentServiceFactory, config: {} },
167
+ ]
168
+ }
169
+
170
+ /** Dedupes per wrapper instance, so every extraction reports its own stubs. */
171
+ function createStubbedServiceReporter(): (serviceId: string) => void {
172
+ const reportedServiceIds = new Set<string>()
173
+
174
+ return (serviceId) => {
175
+ if (reportedServiceIds.has(serviceId)) {
176
+ return
177
+ }
178
+ reportedServiceIds.add(serviceId)
179
+ console.warn(
180
+ `[zero-config] Service "${serviceId}" is not registered by the extraction wrapper and was replaced with an inert stub. Branches depending on it may be missing from the extracted manifest.`,
181
+ )
182
+ }
183
+ }
184
+
185
+ /** Local replacement for `@wix/builder-services-wrapper`'s `WixServicesWrapper`. */
186
+ export function WixServicesWrapper({ children }: { children?: ReactNode }): React.ReactElement {
187
+ // Memoized: the manager eagerly constructs every registered service on creation.
188
+ const servicesManager = React.useMemo(
189
+ () =>
190
+ createServicesManager(
191
+ createServicesMap(createServiceBindings()),
192
+ createAutoStubFallbackManager({ onServiceStubbed: createStubbedServiceReporter() }),
193
+ ),
194
+ [],
195
+ )
196
+
197
+ return React.createElement(ServicesManagerProvider, { servicesManager }, children)
198
+ }
199
+
200
+ export function wrapWithWixServices(Component: ComponentType<unknown>): ComponentType<unknown> {
201
+ const WrappedComponent: ComponentType<unknown> = (props) =>
202
+ React.createElement(WixServicesWrapper, null, React.createElement(Component, props as Record<string, unknown>))
203
+ return WrappedComponent
204
+ }
package/src/index.ts CHANGED
@@ -1,4 +1,3 @@
1
- import { WixServicesWrapper } from '@wix/builder-services-wrapper'
2
1
  import type { EditorReactComponent } from '@wix/react-component-schema'
3
2
  import { Result, ResultAsync } from 'neverthrow'
4
3
  import React, { type ComponentType } from 'react'
@@ -9,6 +8,7 @@ import { buildContextProviderModules } from './extensions/context-providers/cont
9
8
  import { buildContextAwareWrapper, loadMockProviders } from './extensions/context-providers/mock-provider'
10
9
  import { buildRefElementContext } from './extensions/ref-elements/context'
11
10
  import type { RefElementContext } from './extensions/ref-elements/types'
11
+ import { wrapWithWixServices } from './extensions/wix-services/wrapper'
12
12
  import type { ExtractionError } from './extraction-types'
13
13
  import type { RunExtractorsOptions } from './information-extractors/react'
14
14
  import { extractCssImports, extractDefaultComponentInfo } from './information-extractors/ts'
@@ -17,11 +17,7 @@ import { processComponent } from './manifest-pipeline'
17
17
  import { findComponent, findDefaultComponent, loadModuleForExtraction } from './module-loader'
18
18
  import { compileTsFile } from './ts-compiler'
19
19
 
20
- const defaultWrapper = (Component: ComponentType<unknown>): ComponentType<unknown> => {
21
- const WrappedComponent: ComponentType<unknown> = (props) =>
22
- React.createElement(WixServicesWrapper, null, React.createElement(Component, props as Record<string, unknown>))
23
- return WrappedComponent
24
- }
20
+ const defaultWrapper = wrapWithWixServices
25
21
 
26
22
  // ─────────────────────────────────────────────────────────────────────────────
27
23
  // Types