@sitecore-content-sdk/core 0.2.0-beta.2 → 0.2.0-beta.21

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 (88) hide show
  1. package/content.d.ts +1 -0
  2. package/content.js +1 -0
  3. package/dist/cjs/client/graphql-edge-proxy.js +3 -3
  4. package/dist/cjs/client/sitecore-client.js +34 -17
  5. package/dist/cjs/config/define-config.js +6 -5
  6. package/dist/cjs/constants.js +12 -1
  7. package/dist/cjs/content/content-client.js +148 -0
  8. package/dist/cjs/content/index.js +13 -0
  9. package/dist/cjs/content/locales.js +32 -0
  10. package/dist/cjs/content/taxonomies.js +78 -0
  11. package/dist/cjs/content/utils.js +16 -0
  12. package/dist/cjs/debug.js +1 -0
  13. package/dist/cjs/editing/design-library.js +2 -1
  14. package/dist/cjs/editing/rest-component-layout-service.js +26 -45
  15. package/dist/cjs/index.js +3 -1
  16. package/dist/cjs/layout/content-styles.js +2 -1
  17. package/dist/cjs/layout/themes.js +2 -1
  18. package/dist/cjs/site/graphql-robots-service.js +3 -2
  19. package/dist/cjs/tools/auth/encryption.js +141 -0
  20. package/dist/cjs/tools/auth/fetcher.js +34 -0
  21. package/dist/cjs/tools/auth/flow.js +123 -0
  22. package/dist/cjs/tools/auth/index.js +27 -0
  23. package/dist/cjs/tools/auth/models.js +2 -0
  24. package/dist/cjs/tools/auth/renewal.js +130 -0
  25. package/dist/cjs/tools/auth/tenant-state.js +110 -0
  26. package/dist/cjs/tools/auth/tenant-store.js +250 -0
  27. package/dist/cjs/tools/index.js +26 -1
  28. package/dist/cjs/utils/normalize-url.js +5 -0
  29. package/dist/cjs/utils/utils.js +5 -3
  30. package/dist/esm/client/graphql-edge-proxy.js +1 -1
  31. package/dist/esm/client/sitecore-client.js +34 -17
  32. package/dist/esm/config/define-config.js +6 -5
  33. package/dist/esm/constants.js +11 -0
  34. package/dist/esm/content/content-client.js +141 -0
  35. package/dist/esm/content/index.js +4 -0
  36. package/dist/esm/content/locales.js +29 -0
  37. package/dist/esm/content/taxonomies.js +75 -0
  38. package/dist/esm/content/utils.js +13 -0
  39. package/dist/esm/debug.js +1 -0
  40. package/dist/esm/editing/design-library.js +2 -1
  41. package/dist/esm/editing/rest-component-layout-service.js +23 -45
  42. package/dist/esm/index.js +2 -0
  43. package/dist/esm/layout/content-styles.js +2 -1
  44. package/dist/esm/layout/themes.js +2 -1
  45. package/dist/esm/site/graphql-robots-service.js +3 -2
  46. package/dist/esm/tools/auth/encryption.js +101 -0
  47. package/dist/esm/tools/auth/fetcher.js +31 -0
  48. package/dist/esm/tools/auth/flow.js +118 -0
  49. package/dist/esm/tools/auth/index.js +5 -0
  50. package/dist/esm/tools/auth/models.js +1 -0
  51. package/dist/esm/tools/auth/renewal.js +124 -0
  52. package/dist/esm/tools/auth/tenant-state.js +73 -0
  53. package/dist/esm/tools/auth/tenant-store.js +213 -0
  54. package/dist/esm/tools/index.js +3 -0
  55. package/dist/esm/utils/normalize-url.js +1 -0
  56. package/dist/esm/utils/utils.js +5 -3
  57. package/package.json +19 -18
  58. package/types/client/index.d.ts +1 -1
  59. package/types/client/models.d.ts +17 -1
  60. package/types/client/sitecore-client.d.ts +50 -22
  61. package/types/config/index.d.ts +1 -1
  62. package/types/config/models.d.ts +12 -2
  63. package/types/constants.d.ts +10 -0
  64. package/types/content/content-client.d.ts +92 -0
  65. package/types/content/index.d.ts +4 -0
  66. package/types/content/locales.d.ts +38 -0
  67. package/types/content/taxonomies.d.ts +125 -0
  68. package/types/content/utils.d.ts +15 -0
  69. package/types/debug.d.ts +1 -0
  70. package/types/editing/rest-component-layout-service.d.ts +23 -58
  71. package/types/index.d.ts +2 -0
  72. package/types/native-fetcher.d.ts +0 -7
  73. package/types/site/graphql-robots-service.d.ts +3 -2
  74. package/types/tools/auth/encryption.d.ts +34 -0
  75. package/types/tools/auth/fetcher.d.ts +13 -0
  76. package/types/tools/auth/flow.d.ts +40 -0
  77. package/types/tools/auth/index.d.ts +5 -0
  78. package/types/tools/auth/models.d.ts +233 -0
  79. package/types/tools/auth/renewal.d.ts +36 -0
  80. package/types/tools/auth/tenant-state.d.ts +21 -0
  81. package/types/tools/auth/tenant-store.d.ts +63 -0
  82. package/types/tools/index.d.ts +3 -0
  83. package/types/utils/normalize-url.d.ts +1 -0
  84. package/dist/cjs/data-fetcher.js +0 -22
  85. package/dist/esm/data-fetcher.js +0 -17
  86. package/form.d.ts +0 -1
  87. package/form.js +0 -1
  88. package/types/data-fetcher.d.ts +0 -34
@@ -0,0 +1,124 @@
1
+ import { getActiveTenant, clearActiveTenant } from './tenant-state';
2
+ import { clientCredentialsFlow } from './flow';
3
+ import { writeTenantAuthInfo, readTenantAuthInfo, deleteTenantAuthInfo, readTenantInfo, } from './tenant-store';
4
+ import { deleteKey } from './encryption';
5
+ import { DEFAULT_SITECORE_AUTH_DOMAIN, REFRESH_GRANT_TYPE } from './../../constants';
6
+ import { decodeJwtPayload } from './tenant-store';
7
+ import { sendPostRequest } from './fetcher';
8
+ /**
9
+ * Requests a new access token using the OAuth 2.0 refresh token grant type.
10
+ * This is used to "upgrade" an initial device flow token by including tenant-specific context.
11
+ * @param {RefreshTokenRequest} options - Configuration for the refresh token request.
12
+ * @returns {Promise<any>} A promise that resolves to the refreshed token data including tenant context.
13
+ * @throws {Error} If the token request fails or returns an error response.
14
+ */
15
+ export let getRefreshAccessToken = _getRefreshAccessToken;
16
+ export const unitMocks = {
17
+ get getRefreshAccessToken() {
18
+ return _getRefreshAccessToken;
19
+ },
20
+ set getRefreshAccessToken(mockFn) {
21
+ getRefreshAccessToken = mockFn;
22
+ },
23
+ };
24
+ /**
25
+ * Validates whether a given auth config is still valid (i.e., not expired).
26
+ * @param {TenantAuth} authInfo - The tenant auth configuration.
27
+ * @returns True if the token is still valid, false if expired.
28
+ */
29
+ export function validateAuthInfo(authInfo) {
30
+ const now = new Date();
31
+ const expiry = new Date(authInfo.expires_at);
32
+ return now < expiry;
33
+ }
34
+ /**
35
+ * Renews the token for a given tenant using stored credentials.
36
+ * @param {TenantAuth} authInfo - Current authentication info for the tenant.
37
+ * @param {TenantInfo} tenantInfo - Public metadata about the tenant (e.g., clientId).
38
+ * @returns {Promise<void>} resolving when the token is successfully renewed.
39
+ * @throws If credentials are missing or renewal fails.
40
+ */
41
+ export async function renewClientToken(authInfo, tenantInfo) {
42
+ const result = await clientCredentialsFlow({
43
+ clientId: tenantInfo.clientId,
44
+ clientSecret: authInfo.clientSecret,
45
+ organizationId: tenantInfo.organizationId,
46
+ tenantId: tenantInfo.tenantId,
47
+ audience: tenantInfo.audience,
48
+ authority: tenantInfo.authority,
49
+ baseUrl: tenantInfo.baseUrl,
50
+ });
51
+ const tenantId = tenantInfo.tenantId;
52
+ await writeTenantAuthInfo(tenantId, {
53
+ clientSecret: authInfo.clientSecret,
54
+ access_token: result.data.access_token,
55
+ expires_in: result.data.expires_in,
56
+ expires_at: new Date(Date.now() + result.data.expires_in * 1000).toISOString(),
57
+ });
58
+ console.log(`\n Token for tenant ${tenantId} renewed.`);
59
+ }
60
+ /**
61
+ * Ensures a valid token exists, renews it if expired.
62
+ * @returns {Promise<{ tenantId: string } | null>} returns tenant context if successful, otherwise null.
63
+ * @throws If renewal fails or credentials are missing.
64
+ */
65
+ export async function validateAndRenewAuthIfExpired() {
66
+ const tenantId = getActiveTenant();
67
+ if (!tenantId)
68
+ return null;
69
+ const authInfo = await readTenantAuthInfo(tenantId);
70
+ if (!authInfo)
71
+ return null;
72
+ const isValid = validateAuthInfo(authInfo);
73
+ if (isValid) {
74
+ return { tenantId };
75
+ }
76
+ const tenantInfo = await readTenantInfo(tenantId);
77
+ if (!tenantInfo)
78
+ return null;
79
+ console.log(`\n Token for tenant ${tenantId} is expired. Renewing...`);
80
+ try {
81
+ if (authInfo.clientSecret) {
82
+ await renewClientToken(authInfo, tenantInfo);
83
+ return { tenantId };
84
+ }
85
+ else if (authInfo.refresh_token) {
86
+ const refreshTokenResponse = await getRefreshAccessToken({
87
+ clientId: tenantInfo.clientId,
88
+ refreshToken: authInfo.refresh_token,
89
+ tenantId: tenantId,
90
+ organizationId: tenantInfo.organizationId,
91
+ authority: tenantInfo.authority,
92
+ });
93
+ await writeTenantAuthInfo(tenantId, Object.assign({ expires_at: new Date(Date.now() + refreshTokenResponse.expires_in * 1000).toISOString() }, refreshTokenResponse));
94
+ console.log(`\n Token for tenant ${tenantId} renewed.`);
95
+ return { tenantId };
96
+ }
97
+ else {
98
+ throw new Error('\n No valid credentials found for token renewal.');
99
+ }
100
+ }
101
+ catch (err) {
102
+ const errorMessage = err instanceof Error ? err.message : 'Unknown error occurred';
103
+ console.error(`\n Failed to renew token for tenant '${tenantId}: ${errorMessage}'`);
104
+ console.log(`\n Cleaning up stale authentication data for tenant '${tenantId}'...`);
105
+ await deleteTenantAuthInfo(tenantId);
106
+ await deleteKey(tenantId);
107
+ clearActiveTenant();
108
+ return null;
109
+ }
110
+ }
111
+ // eslint-disable-next-line jsdoc/require-jsdoc
112
+ async function _getRefreshAccessToken({ clientId, refreshToken, tenantId, organizationId, authority = DEFAULT_SITECORE_AUTH_DOMAIN, }) {
113
+ const params = new URLSearchParams({
114
+ client_id: clientId,
115
+ grant_type: REFRESH_GRANT_TYPE,
116
+ refresh_token: refreshToken,
117
+ tenant_id: tenantId,
118
+ organization_id: organizationId,
119
+ });
120
+ const url = `${authority}/oauth/token`;
121
+ const data = await sendPostRequest(url, params);
122
+ const { tokenTenantName } = decodeJwtPayload(data.access_token) || {};
123
+ return Object.assign(Object.assign({}, data), { tenantName: tokenTenantName });
124
+ }
@@ -0,0 +1,73 @@
1
+ /* eslint-disable jsdoc/require-jsdoc */
2
+ import * as fs from 'fs';
3
+ import * as path from 'path';
4
+ import * as os from 'os';
5
+ const configDir = path.join(os.homedir(), '.sitecore', 'sitecore-tools');
6
+ const settingsFile = path.join(configDir, 'settings.json');
7
+ /**
8
+ * Gets the ID of the currently active tenant from settings.json.
9
+ * @returns The active tenant ID if present, otherwise null.
10
+ */
11
+ export let getActiveTenant = _getActiveTenant;
12
+ /**
13
+ * Clears the currently active tenant from settings.json by deleting the file.
14
+ */
15
+ export let clearActiveTenant = _clearActiveTenant;
16
+ // mock setup for unit tests to make sinon happy and mock-able with esbuild/tsx
17
+ // https://sinonjs.org/how-to/typescript-swc/
18
+ // This, plus the `_` names make the exports writable for sinon
19
+ export const unitMocks = {
20
+ set clearActiveTenant(mockImplementation) {
21
+ clearActiveTenant = mockImplementation;
22
+ },
23
+ get clearActiveTenant() {
24
+ return _clearActiveTenant;
25
+ },
26
+ set getActiveTenant(mockImplementation) {
27
+ getActiveTenant = mockImplementation;
28
+ },
29
+ get getActiveTenant() {
30
+ return _getActiveTenant;
31
+ },
32
+ };
33
+ function _getActiveTenant() {
34
+ var _a;
35
+ if (!fs.existsSync(settingsFile)) {
36
+ return null;
37
+ }
38
+ try {
39
+ const content = fs.readFileSync(settingsFile, 'utf-8');
40
+ const data = JSON.parse(content);
41
+ return (_a = data.activeTenant) !== null && _a !== void 0 ? _a : null;
42
+ }
43
+ catch (error) {
44
+ console.error(`\n Failed to read active tenant: ${error.message}`);
45
+ return null;
46
+ }
47
+ }
48
+ /**
49
+ * Sets the currently active tenant by writing to settings.json.
50
+ * @param {string} tenantId - The tenant ID to set as active.
51
+ */
52
+ export function setActiveTenant(tenantId) {
53
+ try {
54
+ if (!fs.existsSync(configDir)) {
55
+ fs.mkdirSync(configDir, { recursive: true });
56
+ }
57
+ const data = { activeTenant: tenantId };
58
+ fs.writeFileSync(settingsFile, JSON.stringify(data, null, 2));
59
+ }
60
+ catch (error) {
61
+ console.error(`\n Failed to set active tenant '${tenantId}': ${error.message}`);
62
+ }
63
+ }
64
+ function _clearActiveTenant() {
65
+ try {
66
+ if (fs.existsSync(settingsFile)) {
67
+ fs.unlinkSync(settingsFile);
68
+ }
69
+ }
70
+ catch (error) {
71
+ console.error(`\n Failed to clear active tenant: ${error.message}`);
72
+ }
73
+ }
@@ -0,0 +1,213 @@
1
+ /* eslint-disable jsdoc/require-jsdoc */
2
+ import * as fs from 'fs';
3
+ import * as path from 'path';
4
+ import * as os from 'os';
5
+ import { encryptData, decryptData } from './encryption';
6
+ import { CLAIMS } from './../../constants';
7
+ const rootDir = path.join(os.homedir(), '.sitecore', 'sitecore-tools');
8
+ /**
9
+ * Decodes a JWT without verifying its signature.
10
+ * @param {string} token - The access token string.
11
+ * @returns Decoded payload object or null if invalid.
12
+ */
13
+ export let decodeJwtPayload = _decodeJwtPayload;
14
+ /**
15
+ * Write the authentication configuration for a tenant.
16
+ * @param {string} tenantId - The tenant ID.
17
+ * @param {TenantAuth} authInfo - The tenant's auth data.
18
+ */
19
+ export let writeTenantAuthInfo = _writeTenantAuthInfo;
20
+ /**
21
+ * Read the authentication configuration for a tenant.
22
+ * @param {string} tenantId - The tenant ID.
23
+ * @returns Parsed auth config or null if not found or failed to read.
24
+ */
25
+ export let readTenantAuthInfo = _readTenantAuthInfo;
26
+ /**
27
+ * Write the public metadata information for a tenant.
28
+ * @param {TenantInfo} info - The tenant info object.
29
+ */
30
+ export let writeTenantInfo = _writeTenantInfo;
31
+ /**
32
+ * Read the public metadata information for a tenant.
33
+ * @param {string} tenantId - The tenant ID.
34
+ * @returns Parsed tenant info or null if not found or failed to read.
35
+ */
36
+ export let readTenantInfo = _readTenantInfo;
37
+ /**
38
+ * Deletes the stored auth.json file for the given tenant.
39
+ * @param {string} tenantId - The tenant ID.
40
+ */
41
+ export let deleteTenantAuthInfo = _deleteTenantAuthInfo;
42
+ /**
43
+ * Scans the CLI root directory and returns all valid tenant infos.
44
+ * @returns A list of TenantInfo objects found in {tenant-id}/info.json files.
45
+ */
46
+ export let getAllTenantsInfo = _getAllTenantsInfo;
47
+ // mock setup for unit tests to make sinon happy and mock-able with esbuild/tsx
48
+ // https://sinonjs.org/how-to/typescript-swc/
49
+ // This, plus the `_` names make the exports writable for sinon
50
+ export const unitMocks = {
51
+ set decodeJwtPayload(mockImplementation) {
52
+ decodeJwtPayload = mockImplementation;
53
+ },
54
+ get decodeJwtPayload() {
55
+ return _decodeJwtPayload;
56
+ },
57
+ set writeTenantAuthInfo(mockImplementation) {
58
+ writeTenantAuthInfo = mockImplementation;
59
+ },
60
+ get writeTenantAuthInfo() {
61
+ return _writeTenantAuthInfo;
62
+ },
63
+ set readTenantAuthInfo(mockImplementation) {
64
+ readTenantAuthInfo = mockImplementation;
65
+ },
66
+ get readTenantAuthInfo() {
67
+ return _readTenantAuthInfo;
68
+ },
69
+ set writeTenantInfo(mockImplementation) {
70
+ writeTenantInfo = mockImplementation;
71
+ },
72
+ get writeTenantInfo() {
73
+ return _writeTenantInfo;
74
+ },
75
+ set readTenantInfo(mockImplementation) {
76
+ readTenantInfo = mockImplementation;
77
+ },
78
+ get readTenantInfo() {
79
+ return _readTenantInfo;
80
+ },
81
+ set deleteTenantAuthInfo(mockImplementation) {
82
+ deleteTenantAuthInfo = mockImplementation;
83
+ },
84
+ get deleteTenantAuthInfo() {
85
+ return _deleteTenantAuthInfo;
86
+ },
87
+ set getAllTenantsInfo(mockImplementation) {
88
+ getAllTenantsInfo = mockImplementation;
89
+ },
90
+ get getAllTenantsInfo() {
91
+ return _getAllTenantsInfo;
92
+ },
93
+ };
94
+ /**
95
+ * Get the full path to the tenant-specific folder.
96
+ * @param {string} tenantId - The tenant ID.
97
+ * @returns The absolute path to the tenant directory.
98
+ */
99
+ export function getTenantPath(tenantId) {
100
+ return path.join(rootDir, tenantId);
101
+ }
102
+ async function _writeTenantAuthInfo(tenantId, authInfo) {
103
+ try {
104
+ const dir = getTenantPath(tenantId);
105
+ fs.mkdirSync(dir, { recursive: true });
106
+ const encrypted = await encryptData(JSON.stringify(authInfo), tenantId);
107
+ fs.writeFileSync(path.join(dir, 'auth.json'), JSON.stringify(encrypted));
108
+ }
109
+ catch (error) {
110
+ console.error(`\n Failed to write auth.json for tenant '${tenantId}': ${error.message}`);
111
+ }
112
+ }
113
+ async function _readTenantAuthInfo(tenantId) {
114
+ const filePath = path.join(getTenantPath(tenantId), 'auth.json');
115
+ if (!fs.existsSync(filePath))
116
+ return null;
117
+ try {
118
+ const encryptedPayloadRaw = fs.readFileSync(filePath, 'utf8');
119
+ const encryptedPayload = JSON.parse(encryptedPayloadRaw);
120
+ const decryptedData = await decryptData(encryptedPayload, tenantId, true);
121
+ if (decryptedData === null) {
122
+ return null;
123
+ }
124
+ return JSON.parse(decryptedData);
125
+ }
126
+ catch (error) {
127
+ console.error(`\n Failed to read auth.json for tenant '${tenantId}': ${error.message}`);
128
+ return null;
129
+ }
130
+ }
131
+ async function _writeTenantInfo(info) {
132
+ try {
133
+ const dir = getTenantPath(info.tenantId);
134
+ fs.mkdirSync(dir, { recursive: true });
135
+ fs.writeFileSync(path.join(dir, 'info.json'), JSON.stringify(info, null, 2));
136
+ }
137
+ catch (error) {
138
+ console.error(`\n Failed to write info.json for tenant '${info.tenantId}': ${error.message}`);
139
+ }
140
+ }
141
+ async function _readTenantInfo(tenantId) {
142
+ const infoFilePath = path.join(getTenantPath(tenantId), 'info.json');
143
+ if (!fs.existsSync(infoFilePath)) {
144
+ return null;
145
+ }
146
+ try {
147
+ const content = fs.readFileSync(infoFilePath, 'utf-8');
148
+ return JSON.parse(content);
149
+ }
150
+ catch (error) {
151
+ console.error(`\n Failed to read info.json for tenant '${tenantId}': ${error.message}`);
152
+ return null;
153
+ }
154
+ }
155
+ async function _deleteTenantAuthInfo(tenantId) {
156
+ const filePath = path.join(getTenantPath(tenantId), 'auth.json');
157
+ try {
158
+ if (fs.existsSync(filePath)) {
159
+ fs.unlinkSync(filePath);
160
+ }
161
+ }
162
+ catch (error) {
163
+ console.error(`\n Failed to delete auth.json for tenant '${tenantId}': ${error.message}`);
164
+ }
165
+ }
166
+ function _getAllTenantsInfo() {
167
+ if (!fs.existsSync(rootDir))
168
+ return [];
169
+ const subDirs = fs
170
+ .readdirSync(rootDir)
171
+ .filter((entry) => fs.statSync(path.join(rootDir, entry)).isDirectory());
172
+ const tenants = [];
173
+ for (const dir of subDirs) {
174
+ const infoPath = path.join(rootDir, dir, 'info.json');
175
+ if (fs.existsSync(infoPath)) {
176
+ try {
177
+ const content = fs.readFileSync(infoPath, 'utf-8');
178
+ const data = JSON.parse(content);
179
+ if (data.tenantId && data.tenantName && data.organizationId) {
180
+ tenants.push({
181
+ tenantId: data.tenantId,
182
+ tenantName: data.tenantName,
183
+ organizationId: data.organizationId,
184
+ clientId: data.clientId,
185
+ authority: data.authority,
186
+ audience: data.audience,
187
+ baseUrl: data.baseUrl,
188
+ });
189
+ }
190
+ }
191
+ catch (error) {
192
+ console.error('\n Failed to read tenant info file', error.message);
193
+ }
194
+ }
195
+ }
196
+ return tenants;
197
+ }
198
+ function _decodeJwtPayload(token) {
199
+ try {
200
+ const base64Payload = token.split('.')[1];
201
+ const payload = Buffer.from(base64Payload, 'base64').toString('utf-8');
202
+ const decoded = JSON.parse(payload);
203
+ return {
204
+ tokenTenantId: decoded === null || decoded === void 0 ? void 0 : decoded[`${CLAIMS}/tenant_id`],
205
+ tokenOrgId: decoded === null || decoded === void 0 ? void 0 : decoded[`${CLAIMS}/org_id`],
206
+ tokenTenantName: decoded === null || decoded === void 0 ? void 0 : decoded[`${CLAIMS}/tenant_name`],
207
+ };
208
+ }
209
+ catch (error) {
210
+ console.error('\n Failed to decode access token:', error.message);
211
+ return null;
212
+ }
213
+ }
@@ -2,3 +2,6 @@ export { generateSites } from './generateSites';
2
2
  export { generateMetadata } from './generateMetadata';
3
3
  export { scaffoldComponent } from './scaffold';
4
4
  export * from './templating';
5
+ export * from './auth/models';
6
+ import * as auth from './auth';
7
+ export { auth };
@@ -0,0 +1 @@
1
+ export const normalizeUrl = (url) => (url.endsWith('/') ? url.slice(0, -1) : url);
@@ -160,7 +160,7 @@ export const areURLSearchParamsEqual = (params1, params2) => {
160
160
  * @returns {string} - The modified string or regex with non-special "?" characters escaped.
161
161
  */
162
162
  export const escapeNonSpecialQuestionMarks = (input) => {
163
- const regexPattern = /(?<!\\)\?/g; // Match unescaped "?" characters
163
+ const regexPattern = /(\\)?\?/g; // Match "?" that may or may not be preceded by a backslash
164
164
  const negativeLookaheadPattern = /\(\?!$/; // Detect the start of a Negative Lookahead pattern
165
165
  const specialRegexSymbols = /[.*+)\[\]|\(]$/; // Check for special regex symbols before "?"
166
166
  let result = '';
@@ -169,12 +169,14 @@ export const escapeNonSpecialQuestionMarks = (input) => {
169
169
  while ((match = regexPattern.exec(input)) !== null) {
170
170
  const index = match.index; // Position of the "?" in the string
171
171
  const before = input.slice(lastIndex, index); // Context before the "?"
172
+ // Check if "?" is preceded by a backslash (escaped)
173
+ const isEscaped = match[1] !== undefined; // match[1] is the backslash group
172
174
  // Check if "?" is part of a Negative Lookahead
173
175
  const isNegativeLookahead = negativeLookaheadPattern.test(before.slice(-3));
174
176
  // Check if "?" follows a special regex symbol
175
177
  const isSpecialRegexSymbol = specialRegexSymbols.test(before.slice(-1));
176
- if (isNegativeLookahead || isSpecialRegexSymbol) {
177
- // If it's a special case, keep the "?" as is
178
+ if (isEscaped || isNegativeLookahead || isSpecialRegexSymbol) {
179
+ // If it's escaped, part of a Negative Lookahead, or follows a special regex symbol, keep the "?" as is
178
180
  result += input.slice(lastIndex, index + 1);
179
181
  }
180
182
  else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sitecore-content-sdk/core",
3
- "version": "0.2.0-beta.2",
3
+ "version": "0.2.0-beta.21",
4
4
  "main": "dist/cjs/index.js",
5
5
  "module": "dist/esm/index.js",
6
6
  "sideEffects": false,
@@ -15,7 +15,7 @@
15
15
  "test": "mocha \"./src/**/*.test.ts\"",
16
16
  "prepublishOnly": "npm run build",
17
17
  "coverage": "nyc npm test",
18
- "generate-docs": "npx typedoc --plugin typedoc-plugin-markdown --outputFileStrategy Members --parametersFormat table --readme none --out ../../ref-docs/core --entryPoints src/index.ts --entryPoints src/config/index.ts --entryPoints src/form/index.ts --entryPoints src/client/index.ts --entryPoints src/i18n/index.ts --entryPoints src/layout/index.ts --entryPoints src/media/index.ts --entryPoints src/personalize/index.ts --entryPoints src/site/index.ts --entryPoints src/tracking/index.ts --entryPoints src/utils/index.ts --entryPoints src/editing/index.ts --entryPoints src/tools/index.ts --githubPages false"
18
+ "generate-docs": "npx typedoc --plugin typedoc-plugin-markdown --outputFileStrategy Members --parametersFormat table --readme none --out ../../ref-docs/core --entryPoints src/index.ts --entryPoints src/config/index.ts --entryPoints src/client/index.ts --entryPoints src/i18n/index.ts --entryPoints src/layout/index.ts --entryPoints src/media/index.ts --entryPoints src/personalize/index.ts --entryPoints src/site/index.ts --entryPoints src/tracking/index.ts --entryPoints src/utils/index.ts --entryPoints src/editing/index.ts --entryPoints src/tools/index.ts src/content/index.ts --githubPages false"
19
19
  },
20
20
  "engines": {
21
21
  "node": ">=22"
@@ -34,50 +34,51 @@
34
34
  "url": "https://github.com/sitecore/content-sdk/issues"
35
35
  },
36
36
  "devDependencies": {
37
- "@sitecore-cloudsdk/events": "^0.5.0",
38
- "@types/chai": "^5.0.1",
37
+ "@sitecore-cloudsdk/events": "^0.5.1",
38
+ "@types/chai": "^5.2.2",
39
39
  "@types/chai-spies": "^1.0.6",
40
40
  "@types/chai-string": "^1.4.5",
41
41
  "@types/debug": "^4.1.12",
42
42
  "@types/jsdom": "^21.1.7",
43
43
  "@types/memory-cache": "^0.2.6",
44
- "@types/mocha": "^10.0.1",
45
- "@types/node": "^22.12.0",
44
+ "@types/mocha": "^10.0.10",
45
+ "@types/node": "^22.15.14",
46
46
  "@types/proxyquire": "^1.3.31",
47
- "@types/sinon": "^17.0.3",
47
+ "@types/sinon": "^17.0.4",
48
48
  "@types/sinon-chai": "^4.0.0",
49
49
  "@types/url-parse": "1.4.11",
50
- "chai": "^4.2.0",
50
+ "chai": "^4.4.1",
51
51
  "chai-spies": "^1.1.0",
52
- "chai-string": "^1.5.0",
52
+ "chai-string": "^1.6.0",
53
53
  "del-cli": "^6.0.0",
54
54
  "eslint": "^8.56.0",
55
- "eslint-plugin-jsdoc": "48.7.0",
56
- "jsdom": "^26.0.0",
57
- "mocha": "^11.1.0",
55
+ "eslint-plugin-jsdoc": "50.6.11",
56
+ "jsdom": "^26.1.0",
57
+ "mocha": "^11.2.2",
58
58
  "nock": "14.0.0-beta.7",
59
59
  "nyc": "^17.1.0",
60
60
  "proxyquire": "^2.1.3",
61
- "sinon": "^19.0.2",
61
+ "sinon": "^20.0.0",
62
62
  "tslib": "^2.8.1",
63
- "tsx": "^4.19.2",
64
- "typescript": "~5.7.3"
63
+ "tsx": "^4.19.4",
64
+ "typescript": "~5.8.3"
65
65
  },
66
66
  "peerDependencies": {
67
- "@sitecore-cloudsdk/events": "^0.5.0"
67
+ "@sitecore-cloudsdk/events": "^0.5.1"
68
68
  },
69
69
  "dependencies": {
70
70
  "chalk": "^4.1.2",
71
71
  "debug": "^4.4.0",
72
- "graphql": "^16.10.0",
72
+ "graphql": "^16.11.0",
73
73
  "graphql-request": "^6.1.0",
74
+ "keytar": "^7.9.0",
74
75
  "memory-cache": "^0.2.0",
75
76
  "sinon-chai": "^4.0.0",
76
77
  "url-parse": "^1.5.10"
77
78
  },
78
79
  "description": "",
79
80
  "types": "types/index.d.ts",
80
- "gitHead": "6414b2a03a72e9a7c96a4a20de79ede4bafb0290",
81
+ "gitHead": "09fafe89fe05ef49b349bfc139ed6174940564ba",
81
82
  "files": [
82
83
  "dist",
83
84
  "types",
@@ -4,4 +4,4 @@ export { RetryStrategy, PageInfo, FetchOptions } from '../models';
4
4
  export { getEdgeProxyContentUrl, getEdgeProxyFormsUrl } from './graphql-edge-proxy';
5
5
  export { SitecoreClient, Page, PageOptions, SitemapXmlOptions } from './sitecore-client';
6
6
  export { SitecoreClientInit } from './models';
7
- export { createGraphQLClientFactory } from './utils';
7
+ export { createGraphQLClientFactory, GraphQLClientOptions } from './utils';
@@ -1,5 +1,21 @@
1
1
  import { SitecoreConfig } from '../config';
2
- import { SiteInfo } from '../site';
2
+ import { GraphQLEditingService } from '../editing/graphql-editing-service';
3
+ import { RestComponentLayoutService } from '../editing/rest-component-layout-service';
4
+ import { GraphQLDictionaryService } from '../i18n/graphql-dictionary-service';
5
+ import { GraphQLLayoutService } from '../layout/graphql-layout-service';
6
+ import { GraphQLErrorPagesService, GraphQLSitePathService, SiteInfo, SiteResolver } from '../site';
7
+ /**
8
+ * Init options for Sitecore Client that allows you to override services too
9
+ */
3
10
  export type SitecoreClientInit = Omit<SitecoreConfig, 'multisite' | 'redirects' | 'personalize'> & {
4
11
  sites: SiteInfo[];
12
+ custom?: {
13
+ layoutService?: GraphQLLayoutService;
14
+ dictionaryService?: GraphQLDictionaryService;
15
+ siteResolver?: SiteResolver;
16
+ editingService?: GraphQLEditingService;
17
+ errorPagesService?: GraphQLErrorPagesService;
18
+ componentService?: RestComponentLayoutService;
19
+ sitePathService?: GraphQLSitePathService;
20
+ };
5
21
  };