@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,95 @@
1
+ import { expect } from 'chai';
2
+ import { defineCliConfig } from './define-cli-config';
3
+ import {
4
+ SitecoreCliConfigInput,
5
+ SitecoreCliConfig,
6
+ ComponentTemplateType,
7
+ } from '@sitecore-content-sdk/core/config';
8
+ import chalk from 'chalk';
9
+
10
+ describe('defineCliConfig', () => {
11
+ const validateDefaultTemplates = (result: SitecoreCliConfig) => {
12
+ expect(result.scaffold.templates[0].name).to.equal(
13
+ ComponentTemplateType.DEFAULT
14
+ );
15
+ const defaultTemplate =
16
+ result.scaffold.templates[0].generateTemplate('ComponentName');
17
+ // expect(defaultTemplate).to.contain(
18
+ // // eslint-disable-next-line quotes
19
+ // `import { ComponentParams, ComponentRendering } from '@sitecore-content-sdk/nextjs';`
20
+ // );
21
+ expect(defaultTemplate).to.contain('ComponentName');
22
+ if (result.scaffold.templates[0].getNextSteps) {
23
+ const componentpath = 'src/components/ComponentName.astro';
24
+ expect(
25
+ result.scaffold.templates[0].getNextSteps(componentpath)[0]
26
+ ).to.contain(
27
+ `* Implement the Astro component in ${chalk.green(componentpath)}`
28
+ );
29
+ }
30
+
31
+ // expect(result.scaffold.templates[1].name).to.equal(ComponentTemplateType.BYOC);
32
+ // const byocTemplate = result.scaffold.templates[1].generateTemplate('ByocComponentName');
33
+ // expect(byocTemplate).to.contain(
34
+ // // eslint-disable-next-line quotes
35
+ // `import * as FEAAS from '@sitecore-feaas/clientside/react';`
36
+ // );
37
+ // expect(byocTemplate).to.contain('ByocComponentName');
38
+
39
+ // expect(result.scaffold.templates[1].generateTemplate('comp name')).to.contain(
40
+ // // eslint-disable-next-line quotes
41
+ // `import * as FEAAS from '@sitecore-feaas/clientside/react';`
42
+ // );
43
+ // if (result.scaffold.templates[1].getNextSteps) {
44
+ // expect(result.scaffold.templates[1].getNextSteps('componentpath')[0]).to.contain(
45
+ // '* Modify component registration through FEAAS.External.registerComponent if needed'
46
+ // );
47
+ // }
48
+ };
49
+
50
+ it('should add default and byoc scaffold templates', () => {
51
+ const inputConfig: SitecoreCliConfigInput = {
52
+ build: {
53
+ commands: [
54
+ () => {
55
+ return async () => {
56
+ Promise.resolve('test');
57
+ };
58
+ },
59
+ ],
60
+ },
61
+ scaffold: {
62
+ templates: [
63
+ { name: 'existing template', generateTemplate: () => 'test' },
64
+ ],
65
+ },
66
+ };
67
+
68
+ const result = defineCliConfig(inputConfig);
69
+ expect(result.scaffold.templates).to.have.lengthOf(2);
70
+
71
+ validateDefaultTemplates(result);
72
+
73
+ expect(result.scaffold.templates[1].name).to.equal('existing template');
74
+ });
75
+
76
+ it('should initialize scaffold object if not present', () => {
77
+ const inputConfig: SitecoreCliConfigInput = {
78
+ build: {
79
+ commands: [
80
+ () => {
81
+ return async () => {
82
+ Promise.resolve('test');
83
+ };
84
+ },
85
+ ],
86
+ },
87
+ };
88
+
89
+ const result = defineCliConfig(inputConfig);
90
+
91
+ expect(result.scaffold.templates).to.have.lengthOf(1);
92
+
93
+ validateDefaultTemplates(result);
94
+ });
95
+ });
@@ -0,0 +1,50 @@
1
+ import {
2
+ SitecoreCliConfigInput,
3
+ SitecoreCliConfig,
4
+ } from '@sitecore-content-sdk/core/config';
5
+ import { defineCliConfig as defineCliConfigCore } from '@sitecore-content-sdk/core/config-cli';
6
+ // import { byocTemplate } from '../tools/templating/byoc-component';
7
+ import { defaultTemplate } from '../tools/templating/default-component';
8
+ import { generateMap } from '../tools/generate-map';
9
+
10
+ /**
11
+ * Accepts a `SitecoreCliConfigInput` object and returns the Sitecore Content SDK CLI configuration from the specified file,
12
+ * updated with the required default values.
13
+ * @param {SitecoreCliConfigInput} cliConfig the cli configuration provided by the application
14
+ * @returns {SitecoreCliConfig} full sitecore cli configuration to use with cli
15
+ */
16
+ export const defineCliConfig = (
17
+ cliConfig: SitecoreCliConfigInput
18
+ ): SitecoreCliConfig => {
19
+ addDefaultScaffoldTemplates(cliConfig);
20
+ addDefaultComponentMapGenerator(cliConfig);
21
+ return defineCliConfigCore(cliConfig);
22
+ };
23
+
24
+ /**
25
+ * Adds default scaffold templates to the CLI configuration.
26
+ * @param {SitecoreCliConfigInput} cliConfig - The CLI configuration object
27
+ */
28
+ function addDefaultScaffoldTemplates(cliConfig: SitecoreCliConfigInput) {
29
+ if (!cliConfig.scaffold) {
30
+ cliConfig.scaffold = {};
31
+ }
32
+
33
+ if (!cliConfig.scaffold.templates) {
34
+ cliConfig.scaffold.templates = [];
35
+ }
36
+
37
+ cliConfig.scaffold.templates.unshift(defaultTemplate);
38
+ }
39
+
40
+ /**
41
+ * Add the framework-specific implementaion of the component map generator to the CLI configuration.
42
+ * @param {SitecoreCliConfigInput} cliConfig - The CLI configuration object
43
+ */
44
+ function addDefaultComponentMapGenerator(cliConfig: SitecoreCliConfigInput) {
45
+ cliConfig.componentMap = {
46
+ generator: generateMap,
47
+ paths: ['src/components'],
48
+ ...cliConfig.componentMap,
49
+ };
50
+ }
@@ -0,0 +1 @@
1
+ export { defineCliConfig } from './define-cli-config';
package/src/context.ts ADDED
@@ -0,0 +1,68 @@
1
+ import { map } from 'nanostores';
2
+ import { ComponentMap } from './sharedTypes/component-props';
3
+ import { Page } from '@sitecore-content-sdk/core/client';
4
+ import { SitecoreConfig } from '@sitecore-content-sdk/core/config';
5
+ import { DictionaryPhrases } from '@sitecore-content-sdk/core/types/i18n';
6
+
7
+ export const SitecoreContext: any = map({});
8
+
9
+ export interface SitecoreContextProps {
10
+ /**
11
+ * The API configuration defined in the `SitecoreConfig`.
12
+ */
13
+ api: SitecoreConfig['api'];
14
+ /**
15
+ * The component map to use for rendering components.
16
+ */
17
+ componentMap?: ComponentMap;
18
+ /**
19
+ * The page data.
20
+ */
21
+ page: Page;
22
+ }
23
+
24
+ export interface SitecoreDictionarytProps {
25
+ /**
26
+ * The dictionary data.
27
+ */
28
+ dictionary: DictionaryPhrases;
29
+ }
30
+
31
+ export const updateSitecoreContext = ({
32
+ page,
33
+ api,
34
+ componentMap,
35
+ }: SitecoreContextProps) => {
36
+ SitecoreContext.setKey('page', page);
37
+ SitecoreContext.setKey('api', api);
38
+ SitecoreContext.setKey('componentMap', componentMap);
39
+ };
40
+
41
+ export const updateSitecoreDictionary = ({
42
+ dictionary,
43
+ }: SitecoreDictionarytProps) => {
44
+ SitecoreContext.setKey('dictionary', dictionary);
45
+ };
46
+
47
+ export const useSitecore = (): SitecoreContextProps => {
48
+ return {
49
+ page: SitecoreContext.get()['page'],
50
+ api: SitecoreContext.get()['api'],
51
+ };
52
+ };
53
+
54
+ export const useComponentMap = (): ComponentMap => {
55
+ return SitecoreContext.get()['componentMap'];
56
+ };
57
+
58
+ export const useDictionary = () => {
59
+ const t = (key: string): string => {
60
+ const dictionary = SitecoreContext.get()['dictionary'];
61
+ if (!dictionary) {
62
+ return key;
63
+ }
64
+ return dictionary[key];
65
+ };
66
+
67
+ return t;
68
+ };
@@ -0,0 +1,8 @@
1
+ export const QUERY_PARAM_VERCEL_PROTECTION_BYPASS = 'x-vercel-protection-bypass';
2
+ export const QUERY_PARAM_VERCEL_SET_BYPASS_COOKIE = 'x-vercel-set-bypass-cookie';
3
+
4
+ /**
5
+ * Headers that should be passed along to (Editing Chromes handler) SSR request.
6
+ * Note these are in lowercase format to match expected `IncomingHttpHeaders`.
7
+ */
8
+ export const EDITING_PASS_THROUGH_HEADERS = ['authorization', 'cookie'];
@@ -0,0 +1,166 @@
1
+ /* eslint-disable no-unused-expressions */
2
+ import { expect } from 'chai';
3
+ import { EditingConfigMiddleware } from './editing-config-middleware';
4
+ import { QUERY_PARAM_EDITING_SECRET } from '@sitecore-content-sdk/core/editing';
5
+ import { AstroContentSdkComponent } from '../sharedTypes/component-props';
6
+ import { mockRequest as MockRequest, Query } from '../test-data/helpers';
7
+
8
+ const allowedOrigin = 'https://allowed.com';
9
+
10
+ const mockRequest = (
11
+ method: string,
12
+ query?: Query,
13
+ headers?: { [key: string]: string }
14
+ ) => {
15
+ return MockRequest({
16
+ query,
17
+ method,
18
+ headers: {
19
+ origin: allowedOrigin,
20
+ ...headers,
21
+ },
22
+ });
23
+ };
24
+
25
+ const componentsMap = new Map<string, AstroContentSdkComponent>();
26
+ componentsMap.set('TestComponentOne', () => {});
27
+ componentsMap.set('TestComponentTwo', () => {});
28
+ const metadata = { packages: { testPackageOne: '0.1.1' } };
29
+
30
+ const expectedResultWithMetadata = {
31
+ components: ['TestComponentOne', 'TestComponentTwo'],
32
+ packages: { testPackageOne: '0.1.1' },
33
+ editMode: 'metadata',
34
+ };
35
+
36
+ const expectedResultForbidden = {
37
+ message: 'Missing or invalid editing secret',
38
+ };
39
+
40
+ describe('EditingConfigMiddleware', () => {
41
+ const secret = 'jss-editing-secret-mock';
42
+
43
+ beforeEach(() => {
44
+ process.env.SITECORE_EDITING_SECRET = secret;
45
+ process.env.JSS_ALLOWED_ORIGINS = allowedOrigin;
46
+ });
47
+
48
+ after(() => {
49
+ delete process.env.SITECORE_EDITING_SECRET;
50
+ delete process.env.JSS_ALLOWED_ORIGINS;
51
+ });
52
+
53
+ it('should respond with 401 for missing secret', async () => {
54
+ const key = 'wrongkey';
55
+ const query = { key } as Query;
56
+ const req = mockRequest('GET', query);
57
+
58
+ const middleware = new EditingConfigMiddleware({
59
+ components: componentsMap,
60
+ metadata,
61
+ });
62
+ const handler = middleware.getHandler();
63
+
64
+ const res = await handler(req);
65
+
66
+ const body = await res.json();
67
+ expect(res.status).to.equal(401);
68
+ expect(body).to.deep.equal(expectedResultForbidden);
69
+ });
70
+
71
+ it('should stop request and return 401 when CORS match is not met', async () => {
72
+ const req = mockRequest('GET', {}, { origin: 'https://notallowed.com' });
73
+ const middleware = new EditingConfigMiddleware({
74
+ components: componentsMap,
75
+ metadata,
76
+ });
77
+ const handler = middleware.getHandler();
78
+
79
+ const res = await handler(req);
80
+
81
+ const body = await res.json();
82
+ expect(res.status).to.equal(401);
83
+ expect(body).to.deep.equal({ message: 'Invalid origin' });
84
+ });
85
+
86
+ it('should respond with 401 for invalid secret', async () => {
87
+ const key = 'wrongkey';
88
+ const query = { key } as Query;
89
+ query[QUERY_PARAM_EDITING_SECRET] = 'wrongsekret';
90
+ const req = mockRequest('GET', query);
91
+
92
+ const middleware = new EditingConfigMiddleware({
93
+ components: componentsMap,
94
+ metadata,
95
+ });
96
+ const handler = middleware.getHandler();
97
+
98
+ const res = await handler(req);
99
+
100
+ const body = await res.json();
101
+ expect(res.status).to.equal(401);
102
+ expect(body).to.deep.equal(expectedResultForbidden);
103
+ });
104
+
105
+ it('should respond with 204 for preflight OPTIONS request', async () => {
106
+ const query = {} as Query;
107
+ query[QUERY_PARAM_EDITING_SECRET] = secret;
108
+ const req = mockRequest('OPTIONS', query);
109
+
110
+ const middleware = new EditingConfigMiddleware({
111
+ components: componentsMap,
112
+ metadata,
113
+ });
114
+ const handler = middleware.getHandler();
115
+
116
+ const res = await handler(req);
117
+
118
+ expect(res.headers.has('Access-Control-Allow-Origin')).to.be.true;
119
+ expect(res.headers.get('Access-Control-Allow-Origin')).to.equal(
120
+ allowedOrigin
121
+ );
122
+
123
+ expect(res.headers.has('Access-Control-Allow-Methods')).to.be.true;
124
+ expect(res.headers.get('Access-Control-Allow-Methods')).to.equal(
125
+ 'GET, POST, OPTIONS, DELETE, PUT, PATCH'
126
+ );
127
+
128
+ expect(res.headers.has('Access-Control-Allow-Headers')).to.be.true;
129
+ expect(res.headers.get('Access-Control-Allow-Headers')).to.equal(
130
+ 'Content-Type, Authorization'
131
+ );
132
+
133
+ expect(res.status).to.equal(204);
134
+ expect(res.body).to.equal(null);
135
+ });
136
+
137
+ const testEditingConfig = async (
138
+ components: Map<string, AstroContentSdkComponent>,
139
+ expectedResult: {
140
+ components: string[];
141
+ packages: { testPackageOne: string };
142
+ editMode: string;
143
+ }
144
+ ) => {
145
+ const key = 'wrongkey';
146
+ const query = { key } as Query;
147
+ query[QUERY_PARAM_EDITING_SECRET] = secret;
148
+ const req = mockRequest('GET', query);
149
+ const middleware = new EditingConfigMiddleware({ components, metadata });
150
+ const handler = middleware.getHandler();
151
+
152
+ const res = await handler(req);
153
+
154
+ const body = await res.json();
155
+ expect(res.status).to.equal(200);
156
+ expect(body).to.deep.equal(expectedResult);
157
+ };
158
+
159
+ it('should respond with 200 and return config data with components array as argument', async () => {
160
+ await testEditingConfig(componentsMap, expectedResultWithMetadata);
161
+ });
162
+
163
+ it('should respond with 200 and return config data with components map as argument', async () => {
164
+ await testEditingConfig(componentsMap, expectedResultWithMetadata);
165
+ });
166
+ });
@@ -0,0 +1,111 @@
1
+ import {
2
+ EDITING_ALLOWED_ORIGINS,
3
+ QUERY_PARAM_EDITING_SECRET,
4
+ } from '@sitecore-content-sdk/core/editing';
5
+ import { debug } from '@sitecore-content-sdk/core';
6
+ import { Metadata } from '@sitecore-content-sdk/core/editing';
7
+ import { getEnforcedCorsHeaders } from '@sitecore-content-sdk/core/utils';
8
+ import { EditMode } from '@sitecore-content-sdk/core/layout';
9
+ import { getEditingSecret } from '../utils';
10
+ import { AstroContentSdkComponent, ComponentMap } from '../sharedTypes/component-props';
11
+
12
+ export type EditingConfigMiddlewareConfig = {
13
+ /**
14
+ * Components available in the application
15
+ */
16
+ components: ComponentMap<AstroContentSdkComponent>;
17
+ /**
18
+ * Application metadata
19
+ */
20
+ metadata: Metadata;
21
+ };
22
+
23
+ /**
24
+ * Middleware / handler used in the editing config API route in xmcloud add on (e.g. '/api/editing/config')
25
+ * provides configuration information to determine feature compatibility on Pages side.
26
+ */
27
+ export class EditingConfigMiddleware {
28
+ /**
29
+ * @param {EditingConfigMiddlewareConfig} [config] Editing configuration middleware config
30
+ */
31
+ constructor(protected config: EditingConfigMiddlewareConfig) {}
32
+
33
+ /**
34
+ * Gets the API route handler
35
+ * @returns middleware handler
36
+ */
37
+ public getHandler(): (req: Request) => Promise<Response> {
38
+ return this.handler;
39
+ }
40
+
41
+ private handler = async (_req: Request): Promise<Response> => {
42
+ const url = new URL(_req.url.toLowerCase());
43
+ const secret = url.searchParams.get(QUERY_PARAM_EDITING_SECRET);
44
+
45
+ const _res = new Response();
46
+ _res.headers.append('content-type', 'application/json; charset=utf-8');
47
+
48
+ const corsHeaders = getEnforcedCorsHeaders({
49
+ requestMethod: _req.method,
50
+ headers: _req.headers,
51
+ allowedOrigins: EDITING_ALLOWED_ORIGINS,
52
+ });
53
+
54
+ if (!corsHeaders) {
55
+ debug.editing(
56
+ 'invalid origin host - set allowed origins in JSS_ALLOWED_ORIGINS environment variable'
57
+ );
58
+
59
+ return new Response(
60
+ JSON.stringify({
61
+ message: 'Invalid origin',
62
+ }),
63
+ {
64
+ status: 401,
65
+ }
66
+ );
67
+ }
68
+
69
+ Object.keys(corsHeaders).forEach((key) => {
70
+ _res.headers.append(key, corsHeaders[key]);
71
+ });
72
+
73
+ if (secret !== getEditingSecret()) {
74
+ debug.editing('invalid editing secret - sent "%s" expected "%s"', secret, getEditingSecret());
75
+
76
+ return new Response(
77
+ JSON.stringify({
78
+ message: 'Missing or invalid editing secret',
79
+ }),
80
+ {
81
+ status: 401,
82
+ headers: _res.headers,
83
+ }
84
+ );
85
+ }
86
+
87
+ // Handle preflight request
88
+ if (_req.method === 'OPTIONS') {
89
+ debug.editing('preflight request');
90
+
91
+ // CORS headers are set by enforceCors
92
+ return new Response(null, {
93
+ status: 204,
94
+ headers: _res.headers,
95
+ });
96
+ }
97
+
98
+ const components = Array.from(this.config.components.keys());
99
+
100
+ return new Response(
101
+ JSON.stringify({
102
+ components,
103
+ packages: this.config.metadata.packages,
104
+ editMode: EditMode.Metadata,
105
+ }),
106
+ {
107
+ headers: _res.headers,
108
+ }
109
+ );
110
+ };
111
+ }