@salesforce/pwa-kit-create-app 3.11.0 → 3.12.0-dev
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/assets/bootstrap/js/config/default.js.hbs +22 -0
- package/assets/bootstrap/js/config/utils.js.hbs +48 -0
- package/assets/bootstrap/js/overrides/app/components/_app-config/index.jsx.hbs +28 -7
- package/assets/bootstrap/js/overrides/app/constants.js.hbs +3 -3
- package/assets/bootstrap/js/overrides/app/main.jsx +2 -1
- package/assets/bootstrap/js/overrides/app/ssr.js.hbs +40 -20
- package/assets/templates/@salesforce/retail-react-app/app/components/_app-config/index.jsx.hbs +27 -6
- package/assets/templates/@salesforce/retail-react-app/app/ssr.js.hbs +40 -19
- package/assets/templates/@salesforce/retail-react-app/config/default.js.hbs +18 -0
- package/assets/templates/@salesforce/retail-react-app/config/utils.js.hbs +48 -0
- package/package.json +4 -4
- package/program.json +49 -1
- package/scripts/create-mobify-app.js +42 -19
- package/templates/express-minimal.tar.gz +0 -0
- package/templates/mrt-reference-app.tar.gz +0 -0
- package/templates/retail-react-app.tar.gz +0 -0
- package/templates/typescript-minimal.tar.gz +0 -0
- package/assets/cursor-rules/accessibility-rule.mdc +0 -8
- package/assets/cursor-rules/cursor-accessibility-mdc/accessibility-button-name.mdc +0 -26
- package/assets/cursor-rules/cursor-accessibility-mdc/accessibility-heading-order.mdc +0 -26
- package/assets/cursor-rules/cursor-accessibility-mdc/accessibility-image-alt.mdc +0 -24
- package/assets/cursor-rules/cursor-accessibility-mdc/accessibility-input-label.mdc +0 -27
- package/assets/cursor-rules/cursor-accessibility-mdc/accessibility-link-name.mdc +0 -26
- package/assets/cursor-rules/cursor-rule.mdc +0 -66
|
@@ -6,8 +6,26 @@
|
|
|
6
6
|
*/
|
|
7
7
|
/* eslint-disable @typescript-eslint/no-var-requires */
|
|
8
8
|
const sites = require('./sites.js')
|
|
9
|
+
const {parseCommerceAgentSettings} = require('./utils.js')
|
|
10
|
+
|
|
9
11
|
module.exports = {
|
|
10
12
|
app: {
|
|
13
|
+
// Enable the store locator and shop the store feature.
|
|
14
|
+
storeLocatorEnabled: true,
|
|
15
|
+
// Enable the multi-ship feature.
|
|
16
|
+
multishipEnabled: true,
|
|
17
|
+
// Enable partial hydration capabilities via Island component
|
|
18
|
+
{{#if answers.project.partialHydrationEnabled}}
|
|
19
|
+
partialHydrationEnabled: {{answers.project.partialHydrationEnabled}},
|
|
20
|
+
{{else}}
|
|
21
|
+
partialHydrationEnabled: false,
|
|
22
|
+
{{/if}}
|
|
23
|
+
// Commerce shopping agent configuration for embedded messaging service
|
|
24
|
+
// This enables an agentic shopping experience in the application
|
|
25
|
+
// This property accepts either a JSON string or a plain JavaScript object.
|
|
26
|
+
// The value is set from the COMMERCE_AGENT_SETTINGS environment variable.
|
|
27
|
+
// If the COMMERCE_AGENT_SETTINGS environment variable is not set, the feature is disabled.
|
|
28
|
+
commerceAgent: parseCommerceAgentSettings(process.env.COMMERCE_AGENT_SETTINGS),
|
|
11
29
|
// Customize how your 'site' and 'locale' are displayed in the url.
|
|
12
30
|
url: {
|
|
13
31
|
{{#if answers.project.demo.enableDemoSettings}}
|
|
@@ -109,6 +127,10 @@ module.exports = {
|
|
|
109
127
|
tenantId: '{{answers.project.dataCloud.tenantId}}'
|
|
110
128
|
}
|
|
111
129
|
},
|
|
130
|
+
// Experimental: The base path for the app. This is the path that will be prepended to all /mobify routes,
|
|
131
|
+
// callback routes, and Express routes.
|
|
132
|
+
// Setting this to `/` or an empty string will result in the above routes not having a base path.
|
|
133
|
+
envBasePath: '/',
|
|
112
134
|
// This list contains server-side only libraries that you don't want to be compiled by webpack
|
|
113
135
|
externals: [],
|
|
114
136
|
// Page not found url for your app
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2021, salesforce.com, inc.
|
|
3
|
+
* All rights reserved.
|
|
4
|
+
* SPDX-License-Identifier: BSD-3-Clause
|
|
5
|
+
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Safely parses commerce agent settings from either a JSON string or object
|
|
10
|
+
* @param {string|object} settings - The commerce agent settings
|
|
11
|
+
* @returns {object} Parsed commerce agent settings object
|
|
12
|
+
*/
|
|
13
|
+
function parseCommerceAgentSettings(settings) {
|
|
14
|
+
// Default configuration when no settings are provided
|
|
15
|
+
const defaultConfig = {
|
|
16
|
+
enabled: 'false',
|
|
17
|
+
askAgentOnSearch: 'false',
|
|
18
|
+
embeddedServiceName: '',
|
|
19
|
+
embeddedServiceEndpoint: '',
|
|
20
|
+
scriptSourceUrl: '',
|
|
21
|
+
scrt2Url: '',
|
|
22
|
+
salesforceOrgId: '',
|
|
23
|
+
commerceOrgId: '',
|
|
24
|
+
siteId: ''
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// If settings is already an object, return it
|
|
28
|
+
if (typeof settings === 'object' && settings !== null) {
|
|
29
|
+
return settings
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// If settings is a string, try to parse it
|
|
33
|
+
if (typeof settings === 'string') {
|
|
34
|
+
try {
|
|
35
|
+
return JSON.parse(settings)
|
|
36
|
+
} catch (error) {
|
|
37
|
+
console.warn('Invalid COMMERCE_AGENT_SETTINGS format, using defaults:', error.message)
|
|
38
|
+
return defaultConfig
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// If settings is undefined/null, return defaults
|
|
43
|
+
return defaultConfig
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
module.exports = {
|
|
47
|
+
parseCommerceAgentSettings
|
|
48
|
+
}
|
|
@@ -18,15 +18,20 @@ import {
|
|
|
18
18
|
resolveLocaleFromUrl
|
|
19
19
|
} from '@salesforce/retail-react-app/app/utils/site-utils'
|
|
20
20
|
import {getConfig} from '@salesforce/pwa-kit-runtime/utils/ssr-config'
|
|
21
|
-
import {
|
|
21
|
+
import {
|
|
22
|
+
getEnvBasePath,
|
|
23
|
+
slasPrivateProxyPath
|
|
24
|
+
} from '@salesforce/pwa-kit-runtime/utils/ssr-namespace-paths'
|
|
22
25
|
import {createUrlTemplate} from '@salesforce/retail-react-app/app/utils/url'
|
|
23
26
|
import createLogger from '@salesforce/pwa-kit-runtime/utils/logger-factory'
|
|
27
|
+
import {isAbsoluteURL} from '@salesforce/retail-react-app/app/page-designer/utils'
|
|
24
28
|
|
|
25
29
|
import {CommerceApiProvider} from '@salesforce/commerce-sdk-react'
|
|
26
30
|
import {withReactQuery} from '@salesforce/pwa-kit-react-sdk/ssr/universal/components/with-react-query'
|
|
27
31
|
import {useCorrelationId} from '@salesforce/pwa-kit-react-sdk/ssr/universal/hooks'
|
|
28
32
|
import {getAppOrigin} from '@salesforce/pwa-kit-react-sdk/utils/url'
|
|
29
33
|
import {ReactQueryDevtools} from '@tanstack/react-query-devtools'
|
|
34
|
+
import {generateSfdcUserAgent} from '@salesforce/retail-react-app/app/utils/sfdc-user-agent-utils'
|
|
30
35
|
import {
|
|
31
36
|
DEFAULT_DNT_STATE,
|
|
32
37
|
STORE_LOCATOR_RADIUS,
|
|
@@ -38,6 +43,8 @@ import {
|
|
|
38
43
|
STORE_LOCATOR_SUPPORTED_COUNTRIES
|
|
39
44
|
} from '@salesforce/retail-react-app/app/constants'
|
|
40
45
|
|
|
46
|
+
const sfdcUserAgent = generateSfdcUserAgent()
|
|
47
|
+
|
|
41
48
|
/**
|
|
42
49
|
* Use the AppConfig component to inject extra arguments into the getProps
|
|
43
50
|
* methods for all Route Components in the app – typically you'd want to do this
|
|
@@ -49,7 +56,8 @@ import {
|
|
|
49
56
|
const AppConfig = ({children, locals = {}}) => {
|
|
50
57
|
const {correlationId} = useCorrelationId()
|
|
51
58
|
const headers = {
|
|
52
|
-
'correlation-id': correlationId
|
|
59
|
+
'correlation-id': correlationId,
|
|
60
|
+
sfdc_user_agent: sfdcUserAgent
|
|
53
61
|
}
|
|
54
62
|
|
|
55
63
|
const commerceApiConfig = locals.appConfig.commerceAPI
|
|
@@ -68,6 +76,14 @@ const AppConfig = ({children, locals = {}}) => {
|
|
|
68
76
|
supportedCountries: STORE_LOCATOR_SUPPORTED_COUNTRIES
|
|
69
77
|
}
|
|
70
78
|
|
|
79
|
+
// Set absolute uris for CommerceApiProvider proxies and callbacks
|
|
80
|
+
const redirectURI = `${appOrigin}${getEnvBasePath()}/callback`
|
|
81
|
+
const proxy = `${appOrigin}${getEnvBasePath()}${commerceApiConfig.proxyPath}`
|
|
82
|
+
const slasPrivateClientProxyEndpoint = `${appOrigin}${getEnvBasePath()}${slasPrivateProxyPath}`
|
|
83
|
+
const passwordlessLoginCallbackURI = isAbsoluteURL(passwordlessCallback)
|
|
84
|
+
? passwordlessCallback
|
|
85
|
+
: `${appOrigin}${getEnvBasePath()}${passwordlessCallback}`
|
|
86
|
+
|
|
71
87
|
return (
|
|
72
88
|
<CommerceApiProvider
|
|
73
89
|
shortCode={commerceApiConfig.parameters.shortCode}
|
|
@@ -76,17 +92,22 @@ const AppConfig = ({children, locals = {}}) => {
|
|
|
76
92
|
siteId={locals.site?.id}
|
|
77
93
|
locale={locals.locale?.id}
|
|
78
94
|
currency={locals.locale?.preferredCurrency}
|
|
79
|
-
redirectURI={
|
|
80
|
-
proxy={
|
|
95
|
+
redirectURI={redirectURI}
|
|
96
|
+
proxy={proxy}
|
|
81
97
|
headers={headers}
|
|
82
98
|
defaultDnt={DEFAULT_DNT_STATE}
|
|
83
99
|
logger={createLogger({packageName: 'commerce-sdk-react'})}
|
|
84
|
-
passwordlessLoginCallbackURI={
|
|
85
|
-
|
|
86
|
-
// Set 'enablePWAKitPrivateClient' to true use SLAS private client login flows.
|
|
100
|
+
passwordlessLoginCallbackURI={passwordlessLoginCallbackURI}
|
|
101
|
+
// Set 'enablePWAKitPrivateClient' to true to use SLAS private client login flows.
|
|
87
102
|
// Make sure to also enable useSLASPrivateClient in ssr.js when enabling this setting.
|
|
103
|
+
{{#if answers.project.commerce.isSlasPrivate}}
|
|
88
104
|
enablePWAKitPrivateClient={true}
|
|
105
|
+
{{else}}
|
|
106
|
+
enablePWAKitPrivateClient={false}
|
|
89
107
|
{{/if}}
|
|
108
|
+
privateClientProxyEndpoint={slasPrivateClientProxyEndpoint}
|
|
109
|
+
// Uncomment 'hybridAuthEnabled' if the current site has Hybrid Auth enabled. Do NOT set this flag for hybrid storefronts using Plugin SLAS.
|
|
110
|
+
// hybridAuthEnabled={true}
|
|
90
111
|
>
|
|
91
112
|
<MultiSiteProvider site={locals.site} locale={locals.locale} buildUrl={locals.buildUrl}>
|
|
92
113
|
<StoreLocatorProvider config={storeLocatorConfig}>
|
|
@@ -5,10 +5,10 @@
|
|
|
5
5
|
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
/*
|
|
8
|
+
/*
|
|
9
9
|
Hello there! This is a demonstration of how to override a file from the base template.
|
|
10
|
-
|
|
11
|
-
It's necessary that the module export interface remain consistent,
|
|
10
|
+
|
|
11
|
+
It's necessary that the module export interface remain consistent,
|
|
12
12
|
as other files in the base template rely on constants.js, thus we
|
|
13
13
|
import the underlying constants.js, modifies it and re-export it.
|
|
14
14
|
*/
|
|
@@ -5,10 +5,11 @@
|
|
|
5
5
|
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
|
|
6
6
|
*/
|
|
7
7
|
import {start, registerServiceWorker} from '@salesforce/pwa-kit-react-sdk/ssr/browser/main'
|
|
8
|
+
import {getEnvBasePath} from '@salesforce/pwa-kit-runtime/utils/ssr-namespace-paths'
|
|
8
9
|
|
|
9
10
|
const main = () => {
|
|
10
11
|
// The path to your service worker should match what is set up in ssr.js
|
|
11
|
-
return Promise.all([start(), registerServiceWorker(
|
|
12
|
+
return Promise.all([start(), registerServiceWorker(`${getEnvBasePath()}/worker.js`)])
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
main()
|
|
@@ -44,6 +44,13 @@ const options = {
|
|
|
44
44
|
// environment variable as this endpoint will return HTTP 501 if it is not set
|
|
45
45
|
useSLASPrivateClient: {{answers.project.commerce.isSlasPrivate}},
|
|
46
46
|
|
|
47
|
+
// If you wish to use additional SLAS endpoints that require private clients,
|
|
48
|
+
// customize this regex to include the additional endpoints the custom SLAS
|
|
49
|
+
// private client secret handler will inject an Authorization header.
|
|
50
|
+
// The default regex is defined in this file: https://github.com/SalesforceCommerceCloud/pwa-kit/blob/develop/packages/pwa-kit-runtime/src/ssr/server/build-remote-server.js
|
|
51
|
+
// applySLASPrivateClientToEndpoints:
|
|
52
|
+
// /\/oauth2\/(token|passwordless\/(login|token)|password\/(reset|action))/,
|
|
53
|
+
|
|
47
54
|
// If this is enabled, any HTTP header that has a non ASCII value will be URI encoded
|
|
48
55
|
// If there any HTTP headers that have been encoded, an additional header will be
|
|
49
56
|
// passed, `x-encoded-headers`, containing a comma separated list
|
|
@@ -282,32 +289,45 @@ const {handler} = runtime.createHandler(options, (app) => {
|
|
|
282
289
|
// Set default HTTP security headers required by PWA Kit
|
|
283
290
|
app.use(defaultPwaKitSecurityHeaders)
|
|
284
291
|
// Set custom HTTP security headers
|
|
292
|
+
{{#if answers.project.contentSecurityPolicy}}
|
|
293
|
+
const contentSecurityPolicy = {{{json answers.project.contentSecurityPolicy}}}
|
|
294
|
+
{{else}}
|
|
295
|
+
const contentSecurityPolicy = {
|
|
296
|
+
useDefaults: true,
|
|
297
|
+
directives: {
|
|
298
|
+
'img-src': [
|
|
299
|
+
// Default source for product images - replace with your CDN
|
|
300
|
+
'*.commercecloud.salesforce.com',
|
|
301
|
+
'*.demandware.net'
|
|
302
|
+
],
|
|
303
|
+
'script-src': [
|
|
304
|
+
// Used by the service worker in /worker/main.js
|
|
305
|
+
'storage.googleapis.com'
|
|
306
|
+
],
|
|
307
|
+
'connect-src': [
|
|
308
|
+
// Connect to Einstein APIs
|
|
309
|
+
'api.cquotient.com',
|
|
310
|
+
// Connect to DataCloud APIs
|
|
311
|
+
'*.c360a.salesforce.com',
|
|
312
|
+
// Connect to SCRT2 URLs
|
|
313
|
+
'*.salesforce-scrt.com'
|
|
314
|
+
],
|
|
315
|
+
'frame-src': [
|
|
316
|
+
// Allow frames from Salesforce site.com (Needed for MIAW)
|
|
317
|
+
'*.site.com'
|
|
318
|
+
]
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
{{/if}}
|
|
322
|
+
|
|
285
323
|
app.use(
|
|
286
324
|
helmet({
|
|
287
|
-
contentSecurityPolicy
|
|
288
|
-
useDefaults: true,
|
|
289
|
-
directives: {
|
|
290
|
-
'img-src': [
|
|
291
|
-
// Default source for product images - replace with your CDN
|
|
292
|
-
'*.commercecloud.salesforce.com',
|
|
293
|
-
'*.demandware.net'
|
|
294
|
-
],
|
|
295
|
-
'script-src': [
|
|
296
|
-
// Used by the service worker in /worker/main.js
|
|
297
|
-
'storage.googleapis.com'
|
|
298
|
-
],
|
|
299
|
-
'connect-src': [
|
|
300
|
-
// Connect to Einstein APIs
|
|
301
|
-
'api.cquotient.com',
|
|
302
|
-
'*.c360a.salesforce.com'
|
|
303
|
-
]
|
|
304
|
-
}
|
|
305
|
-
}
|
|
325
|
+
contentSecurityPolicy
|
|
306
326
|
})
|
|
307
327
|
)
|
|
308
328
|
|
|
309
329
|
// Handle the redirect from SLAS as to avoid error
|
|
310
|
-
app.get('/callback
|
|
330
|
+
app.get('/callback', (req, res) => {
|
|
311
331
|
// This endpoint does nothing and is not expected to change
|
|
312
332
|
// Thus we cache it for a year to maximize performance
|
|
313
333
|
res.set('Cache-Control', `max-age=31536000`)
|
package/assets/templates/@salesforce/retail-react-app/app/components/_app-config/index.jsx.hbs
CHANGED
|
@@ -19,14 +19,19 @@ import {
|
|
|
19
19
|
resolveLocaleFromUrl
|
|
20
20
|
} from '@salesforce/retail-react-app/app/utils/site-utils'
|
|
21
21
|
import {getConfig} from '@salesforce/pwa-kit-runtime/utils/ssr-config'
|
|
22
|
-
import {
|
|
22
|
+
import {
|
|
23
|
+
getEnvBasePath,
|
|
24
|
+
slasPrivateProxyPath
|
|
25
|
+
} from '@salesforce/pwa-kit-runtime/utils/ssr-namespace-paths'
|
|
23
26
|
import {createUrlTemplate} from '@salesforce/retail-react-app/app/utils/url'
|
|
24
27
|
import createLogger from '@salesforce/pwa-kit-runtime/utils/logger-factory'
|
|
28
|
+
import {isAbsoluteURL} from '@salesforce/retail-react-app/app/page-designer/utils'
|
|
25
29
|
|
|
26
30
|
import {CommerceApiProvider} from '@salesforce/commerce-sdk-react'
|
|
27
31
|
import {withReactQuery} from '@salesforce/pwa-kit-react-sdk/ssr/universal/components/with-react-query'
|
|
28
32
|
import {useCorrelationId} from '@salesforce/pwa-kit-react-sdk/ssr/universal/hooks'
|
|
29
33
|
import {ReactQueryDevtools} from '@tanstack/react-query-devtools'
|
|
34
|
+
import {generateSfdcUserAgent} from '@salesforce/retail-react-app/app/utils/sfdc-user-agent-utils'
|
|
30
35
|
import {DEFAULT_DNT_STATE} from '@salesforce/retail-react-app/app/constants'
|
|
31
36
|
import {
|
|
32
37
|
STORE_LOCATOR_RADIUS,
|
|
@@ -38,6 +43,8 @@ import {
|
|
|
38
43
|
STORE_LOCATOR_SUPPORTED_COUNTRIES
|
|
39
44
|
} from '@salesforce/retail-react-app/app/constants'
|
|
40
45
|
|
|
46
|
+
const sfdcUserAgent = generateSfdcUserAgent()
|
|
47
|
+
|
|
41
48
|
/**
|
|
42
49
|
* Use the AppConfig component to inject extra arguments into the getProps
|
|
43
50
|
* methods for all Route Components in the app – typically you'd want to do this
|
|
@@ -49,7 +56,8 @@ import {
|
|
|
49
56
|
const AppConfig = ({children, locals = {}}) => {
|
|
50
57
|
const {correlationId} = useCorrelationId()
|
|
51
58
|
const headers = {
|
|
52
|
-
'correlation-id': correlationId
|
|
59
|
+
'correlation-id': correlationId,
|
|
60
|
+
sfdc_user_agent: sfdcUserAgent
|
|
53
61
|
}
|
|
54
62
|
|
|
55
63
|
const commerceApiConfig = locals.appConfig.commerceAPI
|
|
@@ -68,6 +76,14 @@ const AppConfig = ({children, locals = {}}) => {
|
|
|
68
76
|
supportedCountries: STORE_LOCATOR_SUPPORTED_COUNTRIES
|
|
69
77
|
}
|
|
70
78
|
|
|
79
|
+
// Set absolute uris for CommerceApiProvider proxies and callbacks
|
|
80
|
+
const redirectURI = `${appOrigin}${getEnvBasePath()}/callback`
|
|
81
|
+
const proxy = `${appOrigin}${getEnvBasePath()}${commerceApiConfig.proxyPath}`
|
|
82
|
+
const slasPrivateClientProxyEndpoint = `${appOrigin}${getEnvBasePath()}${slasPrivateProxyPath}`
|
|
83
|
+
const passwordlessLoginCallbackURI = isAbsoluteURL(passwordlessCallback)
|
|
84
|
+
? passwordlessCallback
|
|
85
|
+
: `${appOrigin}${getEnvBasePath()}${passwordlessCallback}`
|
|
86
|
+
|
|
71
87
|
return (
|
|
72
88
|
<CommerceApiProvider
|
|
73
89
|
shortCode={commerceApiConfig.parameters.shortCode}
|
|
@@ -76,17 +92,22 @@ const AppConfig = ({children, locals = {}}) => {
|
|
|
76
92
|
siteId={locals.site?.id}
|
|
77
93
|
locale={locals.locale?.id}
|
|
78
94
|
currency={locals.locale?.preferredCurrency}
|
|
79
|
-
redirectURI={
|
|
80
|
-
proxy={
|
|
95
|
+
redirectURI={redirectURI}
|
|
96
|
+
proxy={proxy}
|
|
81
97
|
headers={headers}
|
|
82
98
|
defaultDnt={DEFAULT_DNT_STATE}
|
|
83
99
|
logger={createLogger({packageName: 'commerce-sdk-react'})}
|
|
84
100
|
passwordlessLoginCallbackURI={passwordlessCallback}
|
|
85
|
-
|
|
86
|
-
// Set 'enablePWAKitPrivateClient' to true use SLAS private client login flows.
|
|
101
|
+
// Set 'enablePWAKitPrivateClient' to true to use SLAS private client login flows.
|
|
87
102
|
// Make sure to also enable useSLASPrivateClient in ssr.js when enabling this setting.
|
|
103
|
+
{{#if answers.project.commerce.isSlasPrivate}}
|
|
88
104
|
enablePWAKitPrivateClient={true}
|
|
105
|
+
{{else}}
|
|
106
|
+
enablePWAKitPrivateClient={false}
|
|
89
107
|
{{/if}}
|
|
108
|
+
privateClientProxyEndpoint={slasPrivateClientProxyEndpoint}
|
|
109
|
+
// Uncomment 'hybridAuthEnabled' if the current site has Hybrid Auth enabled. Do NOT set this flag for hybrid storefronts using Plugin SLAS.
|
|
110
|
+
// hybridAuthEnabled={true}
|
|
90
111
|
>
|
|
91
112
|
<MultiSiteProvider site={locals.site} locale={locals.locale} buildUrl={locals.buildUrl}>
|
|
92
113
|
<StoreLocatorProvider config={storeLocatorConfig}>
|
|
@@ -44,6 +44,13 @@ const options = {
|
|
|
44
44
|
// environment variable as this endpoint will return HTTP 501 if it is not set
|
|
45
45
|
useSLASPrivateClient: {{answers.project.commerce.isSlasPrivate}},
|
|
46
46
|
|
|
47
|
+
// If you wish to use additional SLAS endpoints that require private clients,
|
|
48
|
+
// customize this regex to include the additional endpoints the custom SLAS
|
|
49
|
+
// private client secret handler will inject an Authorization header.
|
|
50
|
+
// The default regex is defined in this file: https://github.com/SalesforceCommerceCloud/pwa-kit/blob/develop/packages/pwa-kit-runtime/src/ssr/server/build-remote-server.js
|
|
51
|
+
// applySLASPrivateClientToEndpoints:
|
|
52
|
+
// /\/oauth2\/(token|passwordless\/(login|token)|password\/(reset|action))/,
|
|
53
|
+
|
|
47
54
|
// If this is enabled, any HTTP header that has a non ASCII value will be URI encoded
|
|
48
55
|
// If there any HTTP headers that have been encoded, an additional header will be
|
|
49
56
|
// passed, `x-encoded-headers`, containing a comma separated list
|
|
@@ -282,31 +289,45 @@ const {handler} = runtime.createHandler(options, (app) => {
|
|
|
282
289
|
// Set default HTTP security headers required by PWA Kit
|
|
283
290
|
app.use(defaultPwaKitSecurityHeaders)
|
|
284
291
|
// Set custom HTTP security headers
|
|
292
|
+
{{#if answers.project.contentSecurityPolicy}}
|
|
293
|
+
const contentSecurityPolicy = {{{json answers.project.contentSecurityPolicy}}}
|
|
294
|
+
{{else}}
|
|
295
|
+
const contentSecurityPolicy = {
|
|
296
|
+
useDefaults: true,
|
|
297
|
+
directives: {
|
|
298
|
+
'img-src': [
|
|
299
|
+
// Default source for product images - replace with your CDN
|
|
300
|
+
'*.commercecloud.salesforce.com',
|
|
301
|
+
'*.demandware.net'
|
|
302
|
+
],
|
|
303
|
+
'script-src': [
|
|
304
|
+
// Used by the service worker in /worker/main.js
|
|
305
|
+
'storage.googleapis.com'
|
|
306
|
+
],
|
|
307
|
+
'connect-src': [
|
|
308
|
+
// Connect to Einstein APIs
|
|
309
|
+
'api.cquotient.com',
|
|
310
|
+
// Connect to DataCloud APIs
|
|
311
|
+
'*.c360a.salesforce.com',
|
|
312
|
+
// Connect to SCRT2 URLs
|
|
313
|
+
'*.salesforce-scrt.com'
|
|
314
|
+
],
|
|
315
|
+
'frame-src': [
|
|
316
|
+
// Allow frames from Salesforce site.com (Needed for MIAW)
|
|
317
|
+
'*.site.com'
|
|
318
|
+
]
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
{{/if}}
|
|
322
|
+
|
|
285
323
|
app.use(
|
|
286
324
|
helmet({
|
|
287
|
-
contentSecurityPolicy
|
|
288
|
-
useDefaults: true,
|
|
289
|
-
directives: {
|
|
290
|
-
'img-src': [
|
|
291
|
-
// Default source for product images - replace with your CDN
|
|
292
|
-
'*.commercecloud.salesforce.com'
|
|
293
|
-
],
|
|
294
|
-
'script-src': [
|
|
295
|
-
// Used by the service worker in /worker/main.js
|
|
296
|
-
'storage.googleapis.com'
|
|
297
|
-
],
|
|
298
|
-
'connect-src': [
|
|
299
|
-
// Connect to Einstein APIs
|
|
300
|
-
'api.cquotient.com',
|
|
301
|
-
'*.c360a.salesforce.com'
|
|
302
|
-
]
|
|
303
|
-
}
|
|
304
|
-
}
|
|
325
|
+
contentSecurityPolicy
|
|
305
326
|
})
|
|
306
327
|
)
|
|
307
328
|
|
|
308
329
|
// Handle the redirect from SLAS as to avoid error
|
|
309
|
-
app.get('/callback
|
|
330
|
+
app.get('/callback', (req, res) => {
|
|
310
331
|
// This endpoint does nothing and is not expected to change
|
|
311
332
|
// Thus we cache it for a year to maximize performance
|
|
312
333
|
res.set('Cache-Control', `max-age=31536000`)
|
|
@@ -6,8 +6,26 @@
|
|
|
6
6
|
*/
|
|
7
7
|
/* eslint-disable @typescript-eslint/no-var-requires */
|
|
8
8
|
const sites = require('./sites.js')
|
|
9
|
+
const {parseCommerceAgentSettings} = require('./utils.js')
|
|
10
|
+
|
|
9
11
|
module.exports = {
|
|
10
12
|
app: {
|
|
13
|
+
// Enable the store locator and shop the store feature.
|
|
14
|
+
storeLocatorEnabled: true,
|
|
15
|
+
// Enable the multi-ship feature.
|
|
16
|
+
multishipEnabled: true,
|
|
17
|
+
// Enable partial hydration capabilities via Island component
|
|
18
|
+
{{#if answers.project.partialHydrationEnabled}}
|
|
19
|
+
partialHydrationEnabled: {{answers.project.partialHydrationEnabled}},
|
|
20
|
+
{{else}}
|
|
21
|
+
partialHydrationEnabled: false,
|
|
22
|
+
{{/if}}
|
|
23
|
+
// Commerce shopping agent configuration for embedded messaging service
|
|
24
|
+
// This enables an agentic shopping experience in the application
|
|
25
|
+
// This property accepts either a JSON string or a plain JavaScript object.
|
|
26
|
+
// The value is set from the COMMERCE_AGENT_SETTINGS environment variable.
|
|
27
|
+
// If the COMMERCE_AGENT_SETTINGS environment variable is not set, the feature is disabled.
|
|
28
|
+
commerceAgent: parseCommerceAgentSettings(process.env.COMMERCE_AGENT_SETTINGS),
|
|
11
29
|
// Customize settings for your url
|
|
12
30
|
url: {
|
|
13
31
|
{{#if answers.project.demo.enableDemoSettings}}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2021, salesforce.com, inc.
|
|
3
|
+
* All rights reserved.
|
|
4
|
+
* SPDX-License-Identifier: BSD-3-Clause
|
|
5
|
+
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Safely parses commerce agent settings from either a JSON string or object
|
|
10
|
+
* @param {string|object} settings - The commerce agent settings
|
|
11
|
+
* @returns {object} Parsed commerce agent settings object
|
|
12
|
+
*/
|
|
13
|
+
function parseCommerceAgentSettings(settings) {
|
|
14
|
+
// Default configuration when no settings are provided
|
|
15
|
+
const defaultConfig = {
|
|
16
|
+
enabled: 'false',
|
|
17
|
+
askAgentOnSearch: 'false',
|
|
18
|
+
embeddedServiceName: '',
|
|
19
|
+
embeddedServiceEndpoint: '',
|
|
20
|
+
scriptSourceUrl: '',
|
|
21
|
+
scrt2Url: '',
|
|
22
|
+
salesforceOrgId: '',
|
|
23
|
+
commerceOrgId: '',
|
|
24
|
+
siteId: ''
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// If settings is already an object, return it
|
|
28
|
+
if (typeof settings === 'object' && settings !== null) {
|
|
29
|
+
return settings
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// If settings is a string, try to parse it
|
|
33
|
+
if (typeof settings === 'string') {
|
|
34
|
+
try {
|
|
35
|
+
return JSON.parse(settings)
|
|
36
|
+
} catch (error) {
|
|
37
|
+
console.warn('Invalid COMMERCE_AGENT_SETTINGS format, using defaults:', error.message)
|
|
38
|
+
return defaultConfig
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// If settings is undefined/null, return defaults
|
|
43
|
+
return defaultConfig
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
module.exports = {
|
|
47
|
+
parseCommerceAgentSettings
|
|
48
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@salesforce/pwa-kit-create-app",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.12.0-dev",
|
|
4
4
|
"description": "Salesforce's project generator tool",
|
|
5
5
|
"homepage": "https://github.com/SalesforceCommerceCloud/pwa-kit/tree/develop/packages/pwa-kit-create-app#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -39,13 +39,13 @@
|
|
|
39
39
|
"tar": "^6.2.1"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
|
-
"@salesforce/pwa-kit-dev": "3.
|
|
43
|
-
"internal-lib-build": "3.
|
|
42
|
+
"@salesforce/pwa-kit-dev": "3.12.0-dev",
|
|
43
|
+
"internal-lib-build": "3.12.0-dev",
|
|
44
44
|
"verdaccio": "^5.22.1"
|
|
45
45
|
},
|
|
46
46
|
"engines": {
|
|
47
47
|
"node": "^16.11.0 || ^18.0.0 || ^20.0.0 || ^22.0.0",
|
|
48
48
|
"npm": "^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0"
|
|
49
49
|
},
|
|
50
|
-
"gitHead": "
|
|
50
|
+
"gitHead": "60d9a47ad71824709be52bbe1110b092f7698244"
|
|
51
51
|
}
|
package/program.json
CHANGED
|
@@ -472,6 +472,54 @@
|
|
|
472
472
|
},
|
|
473
473
|
"private": true
|
|
474
474
|
},
|
|
475
|
+
{
|
|
476
|
+
"id": "retail-react-app-performance-tests",
|
|
477
|
+
"name": "Retail React App for performance tests",
|
|
478
|
+
"description": "",
|
|
479
|
+
"templateId": "retail-react-app",
|
|
480
|
+
"answers": {
|
|
481
|
+
"project.extend": false,
|
|
482
|
+
"project.hybrid": false,
|
|
483
|
+
"project.name": "retail-react-app",
|
|
484
|
+
"project.commerce.instanceUrl": "https://zzrf-001.dx.commercecloud.salesforce.com",
|
|
485
|
+
"project.commerce.clientId": "9629ef22-f8b8-4987-90ac-b815be3940c8",
|
|
486
|
+
"project.commerce.siteId": "SiteNemesis",
|
|
487
|
+
"project.commerce.organizationId": "f_ecom_tbbn_prd",
|
|
488
|
+
"project.commerce.shortCode": "performance-001",
|
|
489
|
+
"project.commerce.isSlasPrivate": true,
|
|
490
|
+
"project.einstein.clientId": "1ea06c6e-c936-4324-bcf0-fada93f83bb1",
|
|
491
|
+
"project.einstein.siteId": "aaij-MobileFirst",
|
|
492
|
+
"project.dataCloud.appSourceId": "7ae070a6-f4ec-4def-a383-d9cacc3f20a1",
|
|
493
|
+
"project.dataCloud.tenantId": "g82wgnrvm-ywk9dggrrw8mtggy.pc-rnd",
|
|
494
|
+
"project.demo.enableDemoSettings": false,
|
|
495
|
+
|
|
496
|
+
"project.partialHydrationEnabled": true,
|
|
497
|
+
"project.contentSecurityPolicy": {
|
|
498
|
+
"useDefaults": true,
|
|
499
|
+
"directives": {
|
|
500
|
+
"img-src": [
|
|
501
|
+
"*.commercecloud.salesforce.com",
|
|
502
|
+
"*.demandware.net",
|
|
503
|
+
"*.sfcc-store-internal.net"
|
|
504
|
+
],
|
|
505
|
+
"script-src": [
|
|
506
|
+
"storage.googleapis.com",
|
|
507
|
+
"*.sfcc-store-internal.net"
|
|
508
|
+
],
|
|
509
|
+
"connect-src": [
|
|
510
|
+
"api.cquotient.com",
|
|
511
|
+
"*.c360a.salesforce.com",
|
|
512
|
+
"*.salesforce-scrt.com",
|
|
513
|
+
"*.sfcc-store-internal.net"
|
|
514
|
+
],
|
|
515
|
+
"frame-src": [
|
|
516
|
+
"*.site.com"
|
|
517
|
+
]
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
},
|
|
521
|
+
"private": true
|
|
522
|
+
},
|
|
475
523
|
{
|
|
476
524
|
"id": "typescript-minimal-test-project",
|
|
477
525
|
"name": "Template Minimal Test Project",
|
|
@@ -723,7 +771,7 @@
|
|
|
723
771
|
{
|
|
724
772
|
"name": "--stdio",
|
|
725
773
|
"description": "Accept project generation answers from stdin as JSON"
|
|
726
|
-
},
|
|
774
|
+
},
|
|
727
775
|
{
|
|
728
776
|
"name": "--displayProgram",
|
|
729
777
|
"description": "Display the program.json file detailing the program schema/data and exit"
|
|
@@ -91,6 +91,9 @@ sh.set('-e')
|
|
|
91
91
|
// will ensure those escaped double quotes are still escaped after processing the template.
|
|
92
92
|
Handlebars.registerHelper('script', (object) => object.replaceAll('"', '\\"'))
|
|
93
93
|
|
|
94
|
+
// Helper to convert JavaScript objects to JSON strings
|
|
95
|
+
Handlebars.registerHelper('json', (object) => JSON.stringify(object, null, 4))
|
|
96
|
+
|
|
94
97
|
// Validations
|
|
95
98
|
const validPreset = (preset) => {
|
|
96
99
|
return ALL_PRESET_NAMES.includes(preset)
|
|
@@ -111,7 +114,6 @@ const TEMPLATE_SOURCE_BUNDLE = 'bundle'
|
|
|
111
114
|
|
|
112
115
|
const BOOTSTRAP_DIR = p.join(__dirname, '..', 'assets', 'bootstrap', 'js')
|
|
113
116
|
const ASSETS_TEMPLATES_DIR = p.join(__dirname, '..', 'assets', 'templates')
|
|
114
|
-
const CURSOR_RULES_FROM_DIR = p.join(__dirname, '..', 'assets', 'cursor-rules')
|
|
115
117
|
const PRIVATE_PRESET_NAMES = PRESETS.filter(({private}) => !!private).map(({id}) => id)
|
|
116
118
|
const PUBLIC_PRESET_NAMES = PRESETS.filter(({private}) => !private).map(({id}) => id)
|
|
117
119
|
const ALL_PRESET_NAMES = PRIVATE_PRESET_NAMES.concat(PUBLIC_PRESET_NAMES)
|
|
@@ -234,6 +236,38 @@ const expandKey = (key, value) =>
|
|
|
234
236
|
const expandObject = (obj = {}) =>
|
|
235
237
|
Object.keys(obj).reduce((acc, curr) => merge(acc, expandKey(curr, obj[curr])), {})
|
|
236
238
|
|
|
239
|
+
/**
|
|
240
|
+
* If the generated project is based on a template that includes '.cursor/rules',
|
|
241
|
+
* this function copies the rules from the installed node_modules into the
|
|
242
|
+
* top-level of the generated project.
|
|
243
|
+
* @param {string} outputDir - The directory of the generated project
|
|
244
|
+
* @private
|
|
245
|
+
*/
|
|
246
|
+
const copyCursorRules = (outputDir) => {
|
|
247
|
+
const cursorRulesFromDir = p.join(
|
|
248
|
+
outputDir,
|
|
249
|
+
'node_modules',
|
|
250
|
+
'@salesforce',
|
|
251
|
+
'retail-react-app',
|
|
252
|
+
'.cursor',
|
|
253
|
+
'rules'
|
|
254
|
+
)
|
|
255
|
+
if (sh.test('-e', cursorRulesFromDir)) {
|
|
256
|
+
const outputCursorRulesDir = p.join(outputDir, '.cursor', 'rules')
|
|
257
|
+
|
|
258
|
+
// Create the directory if it doesn't exist
|
|
259
|
+
if (!sh.test('-e', outputCursorRulesDir)) {
|
|
260
|
+
fs.mkdirSync(outputCursorRulesDir, {recursive: true})
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Copy the contents of cursorRulesFromDir to outputCursorRulesDir
|
|
264
|
+
const files = fs.readdirSync(cursorRulesFromDir)
|
|
265
|
+
files.forEach((file) => {
|
|
266
|
+
sh.cp('-rf', p.join(cursorRulesFromDir, file), outputCursorRulesDir)
|
|
267
|
+
})
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
237
271
|
/**
|
|
238
272
|
* Envoke the "npm install" command for the provided project directory.
|
|
239
273
|
*
|
|
@@ -352,6 +386,11 @@ const runGenerator = (context, {outputDir, templateVersion, verbose}) => {
|
|
|
352
386
|
assets.forEach((asset) => {
|
|
353
387
|
sh.cp('-rf', p.join(packagePath, asset), outputDir)
|
|
354
388
|
})
|
|
389
|
+
// Install dependencies for the newly minted project.
|
|
390
|
+
npmInstall(outputDir, {verbose})
|
|
391
|
+
|
|
392
|
+
// Extended project does not contain cursor rules and need explicit copy
|
|
393
|
+
copyCursorRules(outputDir)
|
|
355
394
|
} else {
|
|
356
395
|
console.log('Copying base template from package or npm: ', packagePath, outputDir)
|
|
357
396
|
// Copy the base template either from the package or npm.
|
|
@@ -389,26 +428,10 @@ const runGenerator = (context, {outputDir, templateVersion, verbose}) => {
|
|
|
389
428
|
|
|
390
429
|
// Clean up
|
|
391
430
|
sh.rm('-rf', tmp)
|
|
392
|
-
}
|
|
393
431
|
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
const outputCursorRulesDir = p.join(outputDir, '.cursor', 'rules')
|
|
397
|
-
|
|
398
|
-
// Create the directory if it doesn't exist
|
|
399
|
-
if (!sh.test('-e', outputCursorRulesDir)) {
|
|
400
|
-
fs.mkdirSync(outputCursorRulesDir, {recursive: true})
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
// Copy the contents of CURSOR_RULES_FROM_DIR to outputCursorRulesDir
|
|
404
|
-
const files = fs.readdirSync(CURSOR_RULES_FROM_DIR)
|
|
405
|
-
files.forEach((file) => {
|
|
406
|
-
sh.cp('-rf', p.join(CURSOR_RULES_FROM_DIR, file), outputCursorRulesDir)
|
|
407
|
-
})
|
|
432
|
+
// Install dependencies for the newly minted project.
|
|
433
|
+
npmInstall(outputDir, {verbose})
|
|
408
434
|
}
|
|
409
|
-
|
|
410
|
-
// Install dependencies for the newly minted project.
|
|
411
|
-
npmInstall(outputDir, {verbose})
|
|
412
435
|
}
|
|
413
436
|
|
|
414
437
|
const foundNode = process.versions.node
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
description: Check and verify accessibility on local project
|
|
3
|
-
globs:
|
|
4
|
-
alwaysApply: false
|
|
5
|
-
---
|
|
6
|
-
To check accessibility of a project, use the accessibility rules defined in PROJECT_ROOT/.cursor/rules/cursor-accessibility-mdc/ directory.
|
|
7
|
-
Run an automated scan for the entire codebase within the project and check for any violations.
|
|
8
|
-
Show the scanned results in summary and detailed violations if any.
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
description: When accessibility is checked. Check if Buttons must have a discernible name using text or aria attributes
|
|
3
|
-
globs:
|
|
4
|
-
alwaysApply: false
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
# Rule: accessibility-button-name
|
|
8
|
-
|
|
9
|
-
Buttons must have a discernible name using text or aria attributes
|
|
10
|
-
|
|
11
|
-
## 🔍 Pattern
|
|
12
|
-
|
|
13
|
-
```regex
|
|
14
|
-
<button(?![^>]*\b(aria-label|aria-labelledby|title|name)=)
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
## 📍 Examples
|
|
18
|
-
|
|
19
|
-
```tsx
|
|
20
|
-
// ❌ Bad
|
|
21
|
-
<button></button>
|
|
22
|
-
// ✅ Good
|
|
23
|
-
<button>Submit</button>
|
|
24
|
-
// ✅ Good
|
|
25
|
-
<button aria-label="Close dialog"><XIcon /></button>
|
|
26
|
-
```
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
description: When accessibility is checked. Check if heading levels should increase sequentially for semantic structure
|
|
3
|
-
globs:
|
|
4
|
-
alwaysApply: false
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
# Rule: accessibility-heading-order
|
|
8
|
-
|
|
9
|
-
Heading levels should increase sequentially for semantic structure
|
|
10
|
-
|
|
11
|
-
## 🔍 Pattern
|
|
12
|
-
|
|
13
|
-
```regex
|
|
14
|
-
<h([3-6])>
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
## 📍 Examples
|
|
18
|
-
|
|
19
|
-
```tsx
|
|
20
|
-
// ❌ Bad
|
|
21
|
-
<h1>Main Title</h1>
|
|
22
|
-
<h4>Subsection</h4>
|
|
23
|
-
// ✅ Good
|
|
24
|
-
<h1>Main Title</h1>
|
|
25
|
-
<h2>Subsection</h2>
|
|
26
|
-
```
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
description: When accessibility is checked. Ensure all <img> tags include descriptive alt attributes
|
|
3
|
-
globs:
|
|
4
|
-
alwaysApply: false
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
# Rule: accessibility-image-alt
|
|
8
|
-
|
|
9
|
-
Ensure all <img> tags include descriptive alt attributes
|
|
10
|
-
|
|
11
|
-
## 🔍 Pattern
|
|
12
|
-
|
|
13
|
-
```regex
|
|
14
|
-
<img(?![^>]*\balt=)
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
## 📍 Examples
|
|
18
|
-
|
|
19
|
-
```tsx
|
|
20
|
-
// ❌ Bad
|
|
21
|
-
<img src="photo.jpg" />
|
|
22
|
-
// ✅ Good
|
|
23
|
-
<img src="photo.jpg" alt="Company logo" />
|
|
24
|
-
```
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
description: When accessibility is checked. Check if Input fields must have a label or aria-label for screen readers
|
|
3
|
-
globs:
|
|
4
|
-
alwaysApply: false
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
# Rule: accessibility-input-label
|
|
8
|
-
|
|
9
|
-
Input fields must have a label or aria-label for screen readers
|
|
10
|
-
|
|
11
|
-
## 🔍 Pattern
|
|
12
|
-
|
|
13
|
-
```regex
|
|
14
|
-
<input(?![^>]*(aria-label|aria-labelledby|id=))
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
## 📍 Examples
|
|
18
|
-
|
|
19
|
-
```tsx
|
|
20
|
-
// ❌ Bad
|
|
21
|
-
<input type="text" />
|
|
22
|
-
// ✅ Good
|
|
23
|
-
<input aria-label="Search" />
|
|
24
|
-
// ✅ Good with label
|
|
25
|
-
<label htmlFor="email">Email</label>
|
|
26
|
-
<input id="email" type="text" />
|
|
27
|
-
```
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
description: When accessibility is checked. Check if Anchor tags must have accessible names
|
|
3
|
-
globs:
|
|
4
|
-
alwaysApply: false
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
# Rule: accessibility-link-name
|
|
8
|
-
|
|
9
|
-
Anchor tags must have accessible names
|
|
10
|
-
|
|
11
|
-
## 🔍 Pattern
|
|
12
|
-
|
|
13
|
-
```regex
|
|
14
|
-
<a(?![^>]*\b(aria-label|aria-labelledby|title)=)(?![^>]*>\s*\w+\s*</a>)
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
## 📍 Examples
|
|
18
|
-
|
|
19
|
-
```tsx
|
|
20
|
-
// ❌ Bad
|
|
21
|
-
<a href="/profile"></a>
|
|
22
|
-
// ✅ Good
|
|
23
|
-
<a href="/profile">Your Profile</a>
|
|
24
|
-
// ✅ Good
|
|
25
|
-
<a href="/profile" aria-label="Profile page"><UserIcon /></a>
|
|
26
|
-
```
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
description: How to add or edit Cursor rules in our project
|
|
3
|
-
globs:
|
|
4
|
-
alwaysApply: false
|
|
5
|
-
---
|
|
6
|
-
# Cursor Rules Location
|
|
7
|
-
|
|
8
|
-
How to add new cursor rules to the project
|
|
9
|
-
|
|
10
|
-
1. Always place rule files in PROJECT_ROOT/.cursor/rules/:
|
|
11
|
-
```
|
|
12
|
-
.cursor/rules/
|
|
13
|
-
├── your-rule-name.mdc
|
|
14
|
-
├── another-rule.mdc
|
|
15
|
-
└── ...
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
2. Follow the naming convention:
|
|
19
|
-
- Use kebab-case for filenames
|
|
20
|
-
- Always use .mdc extension
|
|
21
|
-
- Make names descriptive of the rule's purpose
|
|
22
|
-
|
|
23
|
-
3. Directory structure:
|
|
24
|
-
```
|
|
25
|
-
PROJECT_ROOT/
|
|
26
|
-
├── .cursor/
|
|
27
|
-
│ └── rules/
|
|
28
|
-
│ ├── your-rule-name.mdc
|
|
29
|
-
│ └── ...
|
|
30
|
-
└── ...
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
4. Never place rule files:
|
|
34
|
-
- In the project root
|
|
35
|
-
- In subdirectories outside .cursor/rules
|
|
36
|
-
- In any other location
|
|
37
|
-
|
|
38
|
-
5. Cursor rules have the following structure:
|
|
39
|
-
|
|
40
|
-
````
|
|
41
|
-
---
|
|
42
|
-
description: Short description of the rule's purpose
|
|
43
|
-
globs: optional/path/pattern/**/*
|
|
44
|
-
alwaysApply: false
|
|
45
|
-
---
|
|
46
|
-
# Rule Title
|
|
47
|
-
|
|
48
|
-
Main content explaining the rule with markdown formatting.
|
|
49
|
-
|
|
50
|
-
1. Step-by-step instructions
|
|
51
|
-
2. Code examples
|
|
52
|
-
3. Guidelines
|
|
53
|
-
|
|
54
|
-
Example:
|
|
55
|
-
```typescript
|
|
56
|
-
// Good example
|
|
57
|
-
function goodExample() {
|
|
58
|
-
// Implementation following guidelines
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// Bad example
|
|
62
|
-
function badExample() {
|
|
63
|
-
// Implementation not following guidelines
|
|
64
|
-
}
|
|
65
|
-
```
|
|
66
|
-
````
|