@exdst-sitecore-content-sdk/astro 0.0.1

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.
Files changed (87) hide show
  1. package/LICENSE.txt +202 -0
  2. package/README.md +3 -0
  3. package/package.json +101 -0
  4. package/src/client/index.ts +12 -0
  5. package/src/client/sitecore-astro-client.test.ts +271 -0
  6. package/src/client/sitecore-astro-client.ts +137 -0
  7. package/src/components/AstroImage.astro +114 -0
  8. package/src/components/Date.astro +76 -0
  9. package/src/components/DefaultEmptyFieldEditingComponentImage.astro +24 -0
  10. package/src/components/DefaultEmptyFieldEditingComponentText.astro +12 -0
  11. package/src/components/EditingScripts.astro +49 -0
  12. package/src/components/EmptyRendering.astro +3 -0
  13. package/src/components/ErrorBoundary.astro +77 -0
  14. package/src/components/FieldMetadata.astro +30 -0
  15. package/src/components/File.astro +46 -0
  16. package/src/components/HiddenRendering.astro +22 -0
  17. package/src/components/Image.astro +155 -0
  18. package/src/components/Link.astro +105 -0
  19. package/src/components/MissingComponent.astro +39 -0
  20. package/src/components/Placeholder/EmptyPlaceholder.astro +9 -0
  21. package/src/components/Placeholder/Placeholder.astro +100 -0
  22. package/src/components/Placeholder/PlaceholderMetadata.astro +102 -0
  23. package/src/components/Placeholder/PlaceholderUtils.astro +153 -0
  24. package/src/components/Placeholder/index.ts +5 -0
  25. package/src/components/Placeholder/models.ts +82 -0
  26. package/src/components/Placeholder/placeholder-utils.test.ts +162 -0
  27. package/src/components/Placeholder/placeholder-utils.ts +80 -0
  28. package/src/components/RenderWrapper.astro +31 -0
  29. package/src/components/RichText.astro +59 -0
  30. package/src/components/Text.astro +97 -0
  31. package/src/components/sharedTypes/index.ts +1 -0
  32. package/src/components/sharedTypes/props.ts +17 -0
  33. package/src/config/define-config.test.ts +526 -0
  34. package/src/config/define-config.ts +99 -0
  35. package/src/config/index.ts +1 -0
  36. package/src/config-cli/define-cli-config.test.ts +95 -0
  37. package/src/config-cli/define-cli-config.ts +50 -0
  38. package/src/config-cli/index.ts +1 -0
  39. package/src/context.ts +68 -0
  40. package/src/editing/constants.ts +8 -0
  41. package/src/editing/editing-config-middleware.test.ts +166 -0
  42. package/src/editing/editing-config-middleware.ts +111 -0
  43. package/src/editing/editing-render-middleware.test.ts +801 -0
  44. package/src/editing/editing-render-middleware.ts +288 -0
  45. package/src/editing/index.ts +16 -0
  46. package/src/editing/render-middleware.test.ts +57 -0
  47. package/src/editing/render-middleware.ts +51 -0
  48. package/src/editing/utils.test.ts +852 -0
  49. package/src/editing/utils.ts +308 -0
  50. package/src/enhancers/WithEmptyFieldEditingComponent.astro +56 -0
  51. package/src/enhancers/WithFieldMetadata.astro +31 -0
  52. package/src/env.d.ts +12 -0
  53. package/src/index.ts +16 -0
  54. package/src/middleware/index.ts +24 -0
  55. package/src/middleware/middleware.test.ts +507 -0
  56. package/src/middleware/middleware.ts +167 -0
  57. package/src/middleware/multisite-middleware.test.ts +672 -0
  58. package/src/middleware/multisite-middleware.ts +147 -0
  59. package/src/middleware/robots-middleware.test.ts +113 -0
  60. package/src/middleware/robots-middleware.ts +47 -0
  61. package/src/middleware/sitemap-middleware.test.ts +152 -0
  62. package/src/middleware/sitemap-middleware.ts +65 -0
  63. package/src/services/component-props-service.ts +182 -0
  64. package/src/sharedTypes/component-props.ts +17 -0
  65. package/src/site/index.ts +1 -0
  66. package/src/test-data/components/Bar.astro +0 -0
  67. package/src/test-data/components/Baz.astro +0 -0
  68. package/src/test-data/components/Foo.astro +0 -0
  69. package/src/test-data/components/Hero.variant.astro +0 -0
  70. package/src/test-data/components/NotComponent.bsx +0 -0
  71. package/src/test-data/components/Qux.astro +0 -0
  72. package/src/test-data/components/folded/Folded.astro +0 -0
  73. package/src/test-data/components/folded/random-file-2.docx +0 -0
  74. package/src/test-data/components/random-file.txt +0 -0
  75. package/src/test-data/helpers.ts +46 -0
  76. package/src/test-data/personalizeData.ts +63 -0
  77. package/src/tools/generate-map.ts +83 -0
  78. package/src/tools/index.ts +8 -0
  79. package/src/tools/templating/components.test.ts +305 -0
  80. package/src/tools/templating/components.ts +49 -0
  81. package/src/tools/templating/constants.ts +4 -0
  82. package/src/tools/templating/default-component.test.ts +31 -0
  83. package/src/tools/templating/default-component.ts +63 -0
  84. package/src/tools/templating/index.ts +2 -0
  85. package/src/utils/index.ts +1 -0
  86. package/src/utils/utils.test.ts +48 -0
  87. package/src/utils/utils.ts +52 -0
@@ -0,0 +1,147 @@
1
+ /* eslint-disable @typescript-eslint/no-non-null-asserted-optional-chain */
2
+ import { getSiteRewrite, SITE_KEY } from '@sitecore-content-sdk/core/site';
3
+ import { debug } from '@sitecore-content-sdk/core';
4
+ import { MiddlewareBase, MiddlewareBaseConfig } from './middleware';
5
+ import { SitecoreConfig } from '../config';
6
+ import { PREVIEW_KEY } from '@sitecore-content-sdk/core/editing';
7
+ import { APIContext, MiddlewareHandler, MiddlewareNext } from 'astro';
8
+ import * as cookie from 'cookie';
9
+
10
+ export type CookieAttributes = {
11
+ /**
12
+ * the Secure attribute of the site cookie
13
+ */
14
+ secure: boolean;
15
+ /**
16
+ * the HttpOnly attribute of the site cookie
17
+ */
18
+ httpOnly: boolean;
19
+ /**
20
+ * the SameSite attribute of the site cookie
21
+ */
22
+ sameSite?: true | false | 'lax' | 'strict' | 'none' | undefined;
23
+ };
24
+
25
+ export type MultisiteMiddlewareConfig = MiddlewareBaseConfig & SitecoreConfig['multisite'];
26
+
27
+ /**
28
+ * Middleware / handler for multisite support
29
+ */
30
+ export class MultisiteMiddleware extends MiddlewareBase {
31
+ /**
32
+ * @param {MultisiteMiddlewareConfig} [config] Multisite middleware config
33
+ */
34
+ constructor(protected config: MultisiteMiddlewareConfig) {
35
+ super(config);
36
+ }
37
+
38
+ handle: MiddlewareHandler = async (context: APIContext, next: MiddlewareNext) => {
39
+ if (this.disabledInChain(context)) {
40
+ debug.multisite(
41
+ 'skipped (multisite middleware is disabled by one of the previous middlewares)'
42
+ );
43
+ return next();
44
+ }
45
+
46
+ try {
47
+ const pathname = context.url.pathname;
48
+ const language = this.getLanguage(context);
49
+ const hostname = this.getHostHeader(context) || this.defaultHostname;
50
+ const startTimestamp = Date.now();
51
+
52
+ debug.multisite('multisite middleware start: %o', {
53
+ pathname,
54
+ language,
55
+ hostname,
56
+ });
57
+
58
+ if (this.isPreview(context)) {
59
+ debug.multisite('skipped (preview)');
60
+
61
+ return next();
62
+ }
63
+
64
+ // Site name preservation is required for Sitecore Preview mode to support navigation between pages
65
+ const isSitecorePreview = context.cookies.get(PREVIEW_KEY)?.value;
66
+
67
+ if (!isSitecorePreview) {
68
+ if (!this.config.enabled) {
69
+ debug.multisite('skipped (multisite middleware is disabled globally)');
70
+ return next();
71
+ }
72
+
73
+ if (this.disabled(context)) {
74
+ debug.multisite('skipped (multisite middleware is disabled)');
75
+
76
+ return next();
77
+ }
78
+ }
79
+
80
+ let siteName: string;
81
+
82
+ if (isSitecorePreview) {
83
+ // This cookie is required to be set in the Sitecore Preview mode to support navigation
84
+ // and preserve the site name
85
+ siteName = context.cookies.get(SITE_KEY)?.value!;
86
+ } else {
87
+ // Site name can be forced by query string parameter or cookie
88
+ siteName =
89
+ context.url.searchParams.get(SITE_KEY) ||
90
+ context.url.searchParams.get('site') ||
91
+ (this.config.useCookieResolution &&
92
+ this.config.useCookieResolution(context.request) &&
93
+ context.cookies.get(SITE_KEY)?.value) ||
94
+ this.siteResolver.getByHost(hostname).name;
95
+ }
96
+
97
+ // Rewrite to site specific path
98
+ const rewritePath = this.getSiteRewrite(pathname, siteName);
99
+
100
+ // Set rewrite header
101
+ const response = await this.rewrite(rewritePath, context, next);
102
+
103
+ // default site cookie attributes
104
+ const defaultCookieAttributes = {
105
+ secure: true,
106
+ httpOnly: true,
107
+ sameSite: 'none',
108
+ } as CookieAttributes;
109
+
110
+ // Share site name with the following executed middlewares
111
+ response.headers.append(
112
+ 'Set-Cookie',
113
+ cookie.serialize(SITE_KEY, siteName, defaultCookieAttributes)
114
+ );
115
+
116
+ debug.multisite('multisite middleware end in %dms: %o', Date.now() - startTimestamp, {
117
+ rewritePath,
118
+ siteName,
119
+ headers: this.extractDebugHeaders(response.headers),
120
+ cookies: response.headers.get('Set-Cookie'),
121
+ });
122
+
123
+ return response;
124
+ } catch (error) {
125
+ console.log('Multisite middleware failed:');
126
+ console.log(error);
127
+ return next();
128
+ }
129
+ };
130
+
131
+ protected disabled(context: APIContext): boolean | undefined {
132
+ // ignore files
133
+ return context.url.pathname.includes('.') || super.disabled(context);
134
+ }
135
+
136
+ /**
137
+ * Generates a site-specific rewrite path based on the provided pathname and site name.
138
+ * @param {string} pathname - The pathname to be rewritten.
139
+ * @param {string} siteName - The name of the site.
140
+ * @returns The rewritten path as a string.
141
+ */
142
+ protected getSiteRewrite(pathname: string, siteName: string): string {
143
+ return getSiteRewrite(pathname, {
144
+ siteName,
145
+ });
146
+ }
147
+ }
@@ -0,0 +1,113 @@
1
+ import * as chai from 'chai';
2
+ import { expect } from 'chai';
3
+ import sinon from 'sinon';
4
+ import sinonChai from 'sinon-chai';
5
+ import { RobotsMiddleware } from './robots-middleware';
6
+ import { SitecoreClient } from '@sitecore-content-sdk/core/client';
7
+ import { SiteInfo } from '@sitecore-content-sdk/core/site';
8
+ import { mockRequest } from '../test-data/helpers';
9
+
10
+ chai.use(sinonChai);
11
+
12
+ describe('RobotsMiddleware', () => {
13
+ const sandbox = sinon.createSandbox();
14
+ let sitecoreClientStub: sinon.SinonStubbedInstance<SitecoreClient>;
15
+ let middleware: RobotsMiddleware;
16
+ let req: Partial<Request>;
17
+ let siteResolverStub = {
18
+ getByHost: sandbox.stub(),
19
+ getByName: sandbox.stub(),
20
+ };
21
+
22
+ const mockSiteInfo: SiteInfo = {
23
+ name: 'test-site',
24
+ hostName: 'example.com',
25
+ language: 'en',
26
+ };
27
+
28
+ const sites = [mockSiteInfo, { name: 'test-site-two', hostName: 'localhost', language: 'da' }];
29
+
30
+ beforeEach(() => {
31
+ sitecoreClientStub = sandbox.createStubInstance(SitecoreClient);
32
+ siteResolverStub = {
33
+ getByHost: sandbox.stub(),
34
+ getByName: sandbox.stub(),
35
+ };
36
+
37
+ req = mockRequest({
38
+ headers: {
39
+ host: 'example.com',
40
+ },
41
+ });
42
+
43
+ middleware = new RobotsMiddleware(sitecoreClientStub as unknown as SitecoreClient, sites);
44
+ (middleware as any).siteResolver = siteResolverStub;
45
+ siteResolverStub.getByHost.callsFake((hostName) =>
46
+ sites.find((site) => site.hostName === hostName)
47
+ );
48
+ });
49
+
50
+ afterEach(() => {
51
+ sandbox.restore();
52
+ });
53
+
54
+ it('should set the content type header to text/plain', async () => {
55
+ sitecoreClientStub.getRobots.resolves('User-agent: *\nDisallow: /');
56
+
57
+ const res = await middleware.getHandler()(req as Request);
58
+
59
+ expect(res.headers.get('Content-Type')).to.equal('text/plain');
60
+ });
61
+
62
+ it('should call getRobots with the correct siteName', async () => {
63
+ sitecoreClientStub.getRobots.resolves('User-agent: *\nDisallow: /');
64
+
65
+ await middleware.getHandler()(req as Request);
66
+
67
+ expect(sitecoreClientStub.getRobots).to.have.been.calledWith('test-site');
68
+ });
69
+
70
+ it('should return 200 with robots content', async () => {
71
+ sitecoreClientStub.getRobots.resolves('User-agent: *\nDisallow: /');
72
+
73
+ const res = await middleware.getHandler()(req as Request);
74
+
75
+ const body = await res.text();
76
+ expect(res.status).to.equal(200);
77
+ expect(body).to.deep.equal('User-agent: *\nDisallow: /');
78
+ });
79
+
80
+ it('should return 404 if getRobots returns null', async () => {
81
+ sitecoreClientStub.getRobots.resolves(undefined);
82
+
83
+ const res = await middleware.getHandler()(req as Request);
84
+
85
+ const body = await res.text();
86
+ expect(res.status).to.equal(404);
87
+ expect(body).to.deep.equal('User-agent: *\nDisallow: /');
88
+ });
89
+
90
+ it('should return 500 if getRobots throws an error', async () => {
91
+ sitecoreClientStub.getRobots.rejects(new Error('Unexpected failure'));
92
+
93
+ const res = await middleware.getHandler()(req as Request);
94
+
95
+ const body = await res.text();
96
+ expect(res.status).to.equal(500);
97
+ expect(body).to.deep.equal('Internal Server Error');
98
+ });
99
+
100
+ it('should use "localhost" as fallback when host header is missing', async () => {
101
+ req = new Request('https://test.com'); // no host header
102
+
103
+ sitecoreClientStub.getRobots.resolves('User-agent: *\nDisallow: /');
104
+
105
+ const res = await middleware.getHandler()(req as Request);
106
+
107
+ expect(sitecoreClientStub.getRobots).to.have.been.calledWith('test-site-two');
108
+
109
+ const body = await res.text();
110
+ expect(res.status).to.equal(200);
111
+ expect(body).to.deep.equal('User-agent: *\nDisallow: /');
112
+ });
113
+ });
@@ -0,0 +1,47 @@
1
+ import { SitecoreClient } from '@sitecore-content-sdk/core/client';
2
+ import { SiteInfo, SiteResolver } from '../site';
3
+
4
+ /**
5
+ * Middleware for handling robots.txt requests.
6
+ */
7
+ export class RobotsMiddleware {
8
+ private client: SitecoreClient;
9
+ private siteResolver: SiteResolver;
10
+
11
+ constructor(client: SitecoreClient, sites: SiteInfo[]) {
12
+ this.client = client;
13
+ this.siteResolver = new SiteResolver(sites);
14
+ }
15
+
16
+ getHandler() {
17
+ return this.handler.bind(this);
18
+ }
19
+
20
+ private async handler(_req: Request): Promise<Response> {
21
+ const _res = new Response();
22
+ _res.headers.append('content-type', 'text/plain');
23
+
24
+ const hostName = _req.headers.get('host')?.split(':')[0] || 'localhost';
25
+ const site = this.siteResolver.getByHost(hostName);
26
+
27
+ try {
28
+ const robotsContent = await this.client.getRobots(site.name);
29
+ if (!robotsContent) {
30
+ return new Response('User-agent: *\nDisallow: /', {
31
+ status: 404,
32
+ headers: _res.headers,
33
+ });
34
+ }
35
+
36
+ return new Response(robotsContent, {
37
+ status: 200,
38
+ headers: _res.headers,
39
+ });
40
+ } catch {
41
+ return new Response('Internal Server Error', {
42
+ status: 500,
43
+ headers: _res.headers,
44
+ });
45
+ }
46
+ }
47
+ }
@@ -0,0 +1,152 @@
1
+ /* eslint-disable no-unused-expressions, @typescript-eslint/no-unused-expressions */
2
+ import * as chai from 'chai';
3
+ import { expect } from 'chai';
4
+ import sinon from 'sinon';
5
+ import sinonChai from 'sinon-chai';
6
+ import { SitemapMiddleware } from './sitemap-middleware';
7
+ import { SitecoreClient } from '@sitecore-content-sdk/core/client';
8
+ import { mockRequest } from '../test-data/helpers';
9
+
10
+ chai.use(sinonChai);
11
+
12
+ describe('SitemapMiddleware', () => {
13
+ const sandbox = sinon.createSandbox();
14
+ let sitecoreClientStub: sinon.SinonStubbedInstance<SitecoreClient>;
15
+ let middleware: SitemapMiddleware;
16
+ let req: Partial<Request>;
17
+ let siteResolverStub = {
18
+ getByHost: sandbox.stub(),
19
+ getByName: sandbox.stub(),
20
+ };
21
+
22
+ const sites = [
23
+ { name: 'test-site', hostName: 'example.com', language: 'en' },
24
+ { name: 'test-site-two', hostName: '*', language: 'da' },
25
+ ];
26
+
27
+ beforeEach(() => {
28
+ sitecoreClientStub = sandbox.createStubInstance(SitecoreClient);
29
+
30
+ req = mockRequest({
31
+ headers: {
32
+ host: 'example.com',
33
+ 'x-forwarded-proto': 'https',
34
+ },
35
+ });
36
+
37
+ siteResolverStub = {
38
+ getByHost: sandbox.stub(),
39
+ getByName: sandbox.stub(),
40
+ };
41
+
42
+ middleware = new SitemapMiddleware(sitecoreClientStub as unknown as SitecoreClient, sites);
43
+ (middleware as any).siteResolver = siteResolverStub;
44
+ siteResolverStub.getByHost.callsFake((hostName) =>
45
+ sites.find((site) => site.hostName === hostName)
46
+ );
47
+ });
48
+
49
+ afterEach(() => {
50
+ sandbox.restore();
51
+ });
52
+
53
+ describe('getHandler', () => {
54
+ it('should return a handler function', () => {
55
+ const handler = middleware.getHandler();
56
+ expect(handler).to.be.a('function');
57
+ });
58
+ });
59
+
60
+ describe('handler', () => {
61
+ it('should process sitemap request without id parameter', async () => {
62
+ const siteName = sites[0].name;
63
+ const xmlContent = '<sitemapindex>...</sitemapindex>';
64
+
65
+ sitecoreClientStub.getSiteMap.resolves(xmlContent);
66
+
67
+ const res = await middleware.getHandler()(req as Request);
68
+
69
+ expect(sitecoreClientStub.getSiteMap.calledOnce).to.be.true;
70
+ expect(sitecoreClientStub.getSiteMap.firstCall.args[0]).to.deep.include({
71
+ reqHost: 'example.com',
72
+ reqProtocol: 'https',
73
+ id: '',
74
+ siteName: siteName,
75
+ });
76
+
77
+ expect(res.headers.get('Content-Type')).to.equal('text/xml;charset=utf-8');
78
+
79
+ const body = await res.text();
80
+ expect(body).to.deep.equal(xmlContent);
81
+ });
82
+
83
+ it('should handle sitemap request with specific id parameter', async () => {
84
+ const sitemapId = '1';
85
+ req = mockRequest({
86
+ url: `https://test.com/sitemap-${sitemapId}.xml`,
87
+ headers: {
88
+ host: 'example.com',
89
+ 'x-forwarded-proto': 'https',
90
+ },
91
+ });
92
+ const siteName = sites[0].name;
93
+ const xmlContent = '<urlset>...</urlset>';
94
+
95
+ sitecoreClientStub.getSiteMap.resolves(xmlContent);
96
+
97
+ const res = await middleware.getHandler()(req as Request);
98
+
99
+ expect(sitecoreClientStub.getSiteMap.firstCall.args[0]).to.deep.include({
100
+ reqHost: 'example.com',
101
+ reqProtocol: 'https',
102
+ id: sitemapId,
103
+ siteName: siteName,
104
+ });
105
+
106
+ const body = await res.text();
107
+ expect(body).to.deep.equal(xmlContent);
108
+ });
109
+
110
+ it('should default to https protocol when x-forwarded-proto header is missing', async () => {
111
+ req.headers?.delete('x-forwarded-proto');
112
+ const siteName = sites[0].name;
113
+ const xmlContent = '<sitemapindex>...</sitemapindex>';
114
+
115
+ sitecoreClientStub.getSiteMap.resolves(xmlContent);
116
+
117
+ await middleware.getHandler()(req as Request);
118
+
119
+ expect(sitecoreClientStub.getSiteMap.firstCall.args[0]).to.deep.include({
120
+ reqHost: 'example.com',
121
+ reqProtocol: 'https',
122
+ siteName: siteName,
123
+ });
124
+ });
125
+
126
+ it('should redirect to 404 when REDIRECT_404 error is thrown', async () => {
127
+ const error = new Error('REDIRECT_404');
128
+
129
+ sitecoreClientStub.getSiteMap.rejects(error);
130
+
131
+ const res = await middleware.getHandler()(req as Request);
132
+
133
+ expect(res.status).to.equal(302);
134
+ expect(res.body).to.equal(null);
135
+
136
+ expect(res.headers.has('Location')).to.be.true;
137
+ expect(res.headers.get('Location')).to.equal('/404');
138
+ });
139
+
140
+ it('should return 500 error when any other error occurs', async () => {
141
+ const error = new Error('Unexpected error');
142
+
143
+ sitecoreClientStub.getSiteMap.rejects(error);
144
+
145
+ const res = await middleware.getHandler()(req as Request);
146
+
147
+ const body = await res.text();
148
+ expect(res.status).to.equal(500);
149
+ expect(body).to.equal('Internal Server Error');
150
+ });
151
+ });
152
+ });
@@ -0,0 +1,65 @@
1
+ import {
2
+ SitecoreClient,
3
+ SitemapXmlOptions,
4
+ } from '@sitecore-content-sdk/core/client';
5
+ import { SiteInfo, SiteResolver } from '../site';
6
+
7
+ /**
8
+ * Middleware for handling sitemap requests.
9
+ * Encapsulates all HTTP-related logic for sitemap generation and delivery.
10
+ */
11
+ export class SitemapMiddleware {
12
+ private client: SitecoreClient;
13
+ private siteResolver: SiteResolver;
14
+
15
+ constructor(client: SitecoreClient, sites: SiteInfo[]) {
16
+ this.client = client;
17
+ this.siteResolver = new SiteResolver(sites);
18
+ }
19
+
20
+ getHandler() {
21
+ return this.handler.bind(this);
22
+ }
23
+
24
+ private async handler(_req: Request): Promise<Response> {
25
+ const url = new URL(_req.url.toLowerCase());
26
+
27
+ const segments = url.pathname.split('/');
28
+ const idMatch = segments[1].match(/(\d+)(?=\.xml$)/);
29
+ const id = idMatch ? idMatch[1] : '';
30
+
31
+ const reqHost = _req.headers.get('host') || '';
32
+ const reqProtocol = _req.headers.get('x-forwarded-proto') || 'https';
33
+ const site = this.siteResolver.getByHost(reqHost);
34
+
35
+ const options: SitemapXmlOptions = {
36
+ reqHost,
37
+ reqProtocol,
38
+ id,
39
+ siteName: site.name,
40
+ };
41
+
42
+ try {
43
+ const xmlContent = await this.client.getSiteMap(options);
44
+
45
+ return new Response(xmlContent, {
46
+ headers: {
47
+ 'Content-Type': 'text/xml;charset=utf-8',
48
+ },
49
+ });
50
+ } catch (error) {
51
+ if (error instanceof Error && error.message === 'REDIRECT_404') {
52
+ return new Response(null, {
53
+ status: 302,
54
+ headers: {
55
+ location: '/404',
56
+ },
57
+ });
58
+ } else {
59
+ return new Response('Internal Server Error', {
60
+ status: 500,
61
+ });
62
+ }
63
+ }
64
+ }
65
+ }
@@ -0,0 +1,182 @@
1
+ // import chalk from 'chalk';
2
+ import {
3
+ LayoutServiceData,
4
+ ComponentRendering,
5
+ PlaceholdersData,
6
+ } from '@sitecore-content-sdk/core/layout';
7
+ import {
8
+ AstroContentSdkComponent,
9
+ ComponentMap,
10
+ ComponentPropsCollection,
11
+ } from '../sharedTypes/component-props';
12
+
13
+ export type FetchComponentPropsArguments = {
14
+ layoutData: LayoutServiceData;
15
+ // context: NextContext;
16
+ components: ComponentMap<AstroContentSdkComponent>;
17
+ };
18
+
19
+ export type ComponentPropsRequest = {
20
+ // fetch: ComponentPropsFetchFunction;
21
+ layoutData: LayoutServiceData;
22
+ rendering: ComponentRendering;
23
+ // context: NextContext;
24
+ };
25
+
26
+ export class ComponentPropsService {
27
+ async fetchComponentProps(
28
+ params: FetchComponentPropsArguments
29
+ ): Promise<ComponentPropsCollection> {
30
+ const { layoutData, components } = params;
31
+ const requests = await this.collectRequests({
32
+ placeholders: layoutData.sitecore.route?.placeholders,
33
+ components,
34
+ layoutData,
35
+ // context,
36
+ });
37
+ return await this.execRequests(requests);
38
+ }
39
+
40
+ /**
41
+ * Go through layout service data, check all renderings using displayName, which should make some side effects.
42
+ * Write result in requests variable
43
+ * @param {object} params params
44
+ * @param {PlaceholdersData} [params.placeholders]
45
+ * @param {ComponentMap} params.components
46
+ * @param {LayoutServiceData} params.layoutData
47
+ * @param {ComponentPropsRequest[]} params.requests
48
+ * @returns {ComponentPropsRequest[]} array of requests
49
+ */
50
+ protected async collectRequests(params: {
51
+ placeholders?: PlaceholdersData;
52
+ components: ComponentMap<AstroContentSdkComponent>;
53
+ layoutData: LayoutServiceData;
54
+ // context: NextContext;
55
+ requests?: ComponentPropsRequest[];
56
+ }): Promise<ComponentPropsRequest[]> {
57
+ const { placeholders = {}, layoutData } = params;
58
+
59
+ // Will be called on first round
60
+ if (!params.requests) {
61
+ params.requests = [];
62
+ }
63
+
64
+ const renderings = this.flatRenderings(placeholders);
65
+
66
+ const actions = renderings.map(async (r) => {
67
+ // const fetchFunc = (await this.getModule(components, r.componentName))
68
+ // ?.getComponentServerProps;
69
+
70
+ const fetchFunc = ""; // getModule
71
+
72
+ if (fetchFunc) {
73
+ params.requests &&
74
+ params.requests.push({
75
+ // fetch: fetchFunc,
76
+ rendering: r,
77
+ layoutData: layoutData,
78
+ // context,
79
+ });
80
+ }
81
+
82
+ // If placeholders exist in current rendering
83
+ if (r.placeholders) {
84
+ await this.collectRequests({
85
+ ...params,
86
+ placeholders: r.placeholders,
87
+ });
88
+ }
89
+ });
90
+
91
+ await Promise.all(actions);
92
+
93
+ return params.requests;
94
+ }
95
+
96
+ /**
97
+ * Execute request for component props
98
+ * @param {ComponentPropsRequest[]} requests requests
99
+ * @returns {Promise<ComponentPropsCollection>} requests result
100
+ */
101
+ protected async execRequests(
102
+ requests: ComponentPropsRequest[]
103
+ ): Promise<ComponentPropsCollection> {
104
+ const componentProps: ComponentPropsCollection = {};
105
+
106
+ const promises = requests.map((req) => {
107
+ const { uid } = req.rendering;
108
+
109
+ if (!uid) {
110
+ console.log(
111
+ `Component ${req.rendering.componentName} doesn't have uid, can't store data for this component`
112
+ );
113
+ return;
114
+ }
115
+
116
+
117
+ // return req
118
+ // .fetch(req.rendering, req.layoutData /*, req.context*/)
119
+ // .then((result) => {
120
+ // // Set component specific data in componentProps store
121
+ // componentProps[uid] = result;
122
+ // })
123
+ // .catch((error) => {
124
+ // const errLog = `Error during preload data for component ${
125
+ // req.rendering.componentName
126
+ // } (${uid}): ${error.message || error}`;
127
+
128
+ // console.error(chalk.red(errLog));
129
+
130
+ // componentProps[uid] = {
131
+ // error: error.message || errLog,
132
+ // componentName: req.rendering.componentName,
133
+ // };
134
+ // });
135
+ });
136
+
137
+ await Promise.all(promises);
138
+
139
+ return componentProps;
140
+ }
141
+
142
+ /**
143
+ * Take renderings from all placeholders and returns a flat array of renderings.
144
+ * @example
145
+ * const placeholders = {
146
+ * x1: [{ uid: 1 }, { uid: 2 }],
147
+ * x2: [{ uid: 11 }, { uid: 22 }]
148
+ * }
149
+ *
150
+ * flatRenderings(placeholders);
151
+ *
152
+ * RESULT: [{ uid: 1 }, { uid: 2 }, { uid: 11 }, { uid: 22 }]
153
+ * @param {PlaceholdersData} placeholders placeholders
154
+ * @returns {ComponentRendering[]} renderings
155
+ */
156
+ protected flatRenderings(
157
+ placeholders: PlaceholdersData
158
+ ): ComponentRendering[] {
159
+ const allComponentRenderings: ComponentRendering[] = [];
160
+ const placeholdersArr = Object.values(placeholders);
161
+
162
+ placeholdersArr.forEach((pl) => {
163
+ const renderings = pl as ComponentRendering[];
164
+ allComponentRenderings.push(...renderings);
165
+ });
166
+
167
+ return allComponentRenderings;
168
+ }
169
+
170
+ // private async getModule(
171
+ // components: ComponentMap<AstroContentSdkComponent>,
172
+ // componentName: string
173
+ // ) {
174
+ // const component = components.get(componentName);
175
+
176
+ // if (!component) return null;
177
+
178
+ // //const module = component.dynamicModule ? await component?.dynamicModule?.() : component;
179
+ // const module = component;
180
+ // return module as AstroContentSdkComponent;
181
+ // }
182
+ }