@schandlergarcia/sf-web-components 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.a4drules/skills/building-data-visualization/SKILL.md +72 -0
- package/.a4drules/skills/building-data-visualization/implementation/bar-line-chart.md +316 -0
- package/.a4drules/skills/building-data-visualization/implementation/dashboard-layout.md +189 -0
- package/.a4drules/skills/building-data-visualization/implementation/donut-chart.md +181 -0
- package/.a4drules/skills/building-data-visualization/implementation/stat-card.md +150 -0
- package/.a4drules/skills/building-react-components/SKILL.md +96 -0
- package/.a4drules/skills/building-react-components/implementation/component.md +78 -0
- package/.a4drules/skills/building-react-components/implementation/header-footer.md +132 -0
- package/.a4drules/skills/building-react-components/implementation/page.md +93 -0
- package/.a4drules/skills/configuring-csp-trusted-sites/SKILL.md +90 -0
- package/.a4drules/skills/configuring-csp-trusted-sites/implementation/metadata-format.md +281 -0
- package/.a4drules/skills/configuring-webapp-metadata/SKILL.md +158 -0
- package/.a4drules/skills/creating-webapp/SKILL.md +140 -0
- package/.a4drules/skills/deploying-to-salesforce/SKILL.md +226 -0
- package/.a4drules/skills/implementing-file-upload/SKILL.md +396 -0
- package/.a4drules/skills/installing-webapp-features/SKILL.md +210 -0
- package/.a4drules/skills/managing-agentforce-conversation-client/SKILL.md +186 -0
- package/.a4drules/skills/managing-agentforce-conversation-client/references/constraints.md +134 -0
- package/.a4drules/skills/managing-agentforce-conversation-client/references/examples.md +132 -0
- package/.a4drules/skills/managing-agentforce-conversation-client/references/style-tokens.md +101 -0
- package/.a4drules/skills/managing-agentforce-conversation-client/references/troubleshooting.md +57 -0
- package/.a4drules/skills/using-salesforce-data/SKILL.md +363 -0
- package/.a4drules/skills/using-salesforce-data/graphql-search.sh +139 -0
- package/.a4drules/webapp-data.md +353 -0
- package/.a4drules/webapp-ui.md +16 -0
- package/README.md +124 -0
- package/dist/components/library/cards/ActionList.js +27 -0
- package/dist/components/library/cards/ActionList.js.map +1 -0
- package/dist/components/library/cards/ActivityCard.js +40 -0
- package/dist/components/library/cards/ActivityCard.js.map +1 -0
- package/dist/components/library/cards/BaseCard.js +89 -0
- package/dist/components/library/cards/BaseCard.js.map +1 -0
- package/dist/components/library/cards/CalloutCard.js +28 -0
- package/dist/components/library/cards/CalloutCard.js.map +1 -0
- package/dist/components/library/cards/ChartCard.js +79 -0
- package/dist/components/library/cards/ChartCard.js.map +1 -0
- package/dist/components/library/cards/FeedPanel.js +38 -0
- package/dist/components/library/cards/FeedPanel.js.map +1 -0
- package/dist/components/library/cards/ListCard.js +112 -0
- package/dist/components/library/cards/ListCard.js.map +1 -0
- package/dist/components/library/cards/MetricCard.js +86 -0
- package/dist/components/library/cards/MetricCard.js.map +1 -0
- package/dist/components/library/cards/MetricsStrip.js +60 -0
- package/dist/components/library/cards/MetricsStrip.js.map +1 -0
- package/dist/components/library/cards/SectionCard.js +59 -0
- package/dist/components/library/cards/SectionCard.js.map +1 -0
- package/dist/components/library/cards/StatusCard.js +137 -0
- package/dist/components/library/cards/StatusCard.js.map +1 -0
- package/dist/components/library/cards/TableCard.js +244 -0
- package/dist/components/library/cards/TableCard.js.map +1 -0
- package/dist/components/library/cards/WidgetCard.js +60 -0
- package/dist/components/library/cards/WidgetCard.js.map +1 -0
- package/dist/components/library/charts/D3Chart.js +74 -0
- package/dist/components/library/charts/D3Chart.js.map +1 -0
- package/dist/components/library/charts/D3ChartTemplates.js +44 -0
- package/dist/components/library/charts/D3ChartTemplates.js.map +1 -0
- package/dist/components/library/charts/GeoMap.js +229 -0
- package/dist/components/library/charts/GeoMap.js.map +1 -0
- package/dist/components/library/chat/ChatBar.js +194 -0
- package/dist/components/library/chat/ChatBar.js.map +1 -0
- package/dist/components/library/chat/ChatInput.js +67 -0
- package/dist/components/library/chat/ChatInput.js.map +1 -0
- package/dist/components/library/chat/ChatMessage.js +112 -0
- package/dist/components/library/chat/ChatMessage.js.map +1 -0
- package/dist/components/library/chat/ChatMessageList.js +50 -0
- package/dist/components/library/chat/ChatMessageList.js.map +1 -0
- package/dist/components/library/chat/ChatPanel.js +77 -0
- package/dist/components/library/chat/ChatPanel.js.map +1 -0
- package/dist/components/library/chat/ChatSuggestions.js +22 -0
- package/dist/components/library/chat/ChatSuggestions.js.map +1 -0
- package/dist/components/library/chat/ChatToolCall.js +87 -0
- package/dist/components/library/chat/ChatToolCall.js.map +1 -0
- package/dist/components/library/chat/ChatTypingIndicator.js +20 -0
- package/dist/components/library/chat/ChatTypingIndicator.js.map +1 -0
- package/dist/components/library/chat/ChatWelcome.js +24 -0
- package/dist/components/library/chat/ChatWelcome.js.map +1 -0
- package/dist/components/library/chat/useChatState.js +77 -0
- package/dist/components/library/chat/useChatState.js.map +1 -0
- package/dist/components/library/data/DataModeProvider.js +47 -0
- package/dist/components/library/data/DataModeProvider.js.map +1 -0
- package/dist/components/library/data/DataModeToggle.js +28 -0
- package/dist/components/library/data/DataModeToggle.js.map +1 -0
- package/dist/components/library/data/filterUtils.js +71 -0
- package/dist/components/library/data/filterUtils.js.map +1 -0
- package/dist/components/library/data/useDataSource.js +13 -0
- package/dist/components/library/data/useDataSource.js.map +1 -0
- package/dist/components/library/data/usePageFilters.js +46 -0
- package/dist/components/library/data/usePageFilters.js.map +1 -0
- package/dist/components/library/filters/FilterBar.js +89 -0
- package/dist/components/library/filters/FilterBar.js.map +1 -0
- package/dist/components/library/filters/SearchFilter.js +44 -0
- package/dist/components/library/filters/SearchFilter.js.map +1 -0
- package/dist/components/library/filters/SelectFilter.js +44 -0
- package/dist/components/library/filters/SelectFilter.js.map +1 -0
- package/dist/components/library/filters/ToggleFilter.js +48 -0
- package/dist/components/library/filters/ToggleFilter.js.map +1 -0
- package/dist/components/library/forms/FormField.js +256 -0
- package/dist/components/library/forms/FormField.js.map +1 -0
- package/dist/components/library/forms/FormModal.js +161 -0
- package/dist/components/library/forms/FormModal.js.map +1 -0
- package/dist/components/library/forms/FormRenderer.js +32 -0
- package/dist/components/library/forms/FormRenderer.js.map +1 -0
- package/dist/components/library/forms/FormSection.js +49 -0
- package/dist/components/library/forms/FormSection.js.map +1 -0
- package/dist/components/library/forms/useFormState.js +85 -0
- package/dist/components/library/forms/useFormState.js.map +1 -0
- package/dist/components/library/heroui/Accordion.js +12 -0
- package/dist/components/library/heroui/Accordion.js.map +1 -0
- package/dist/components/library/heroui/Alert.js +12 -0
- package/dist/components/library/heroui/Alert.js.map +1 -0
- package/dist/components/library/heroui/Badge.js +12 -0
- package/dist/components/library/heroui/Badge.js.map +1 -0
- package/dist/components/library/heroui/Breadcrumbs.js +12 -0
- package/dist/components/library/heroui/Breadcrumbs.js.map +1 -0
- package/dist/components/library/heroui/Button.js +18 -0
- package/dist/components/library/heroui/Button.js.map +1 -0
- package/dist/components/library/heroui/Card.js +12 -0
- package/dist/components/library/heroui/Card.js.map +1 -0
- package/dist/components/library/heroui/Drawer.js +12 -0
- package/dist/components/library/heroui/Drawer.js.map +1 -0
- package/dist/components/library/heroui/Dropdown.js +12 -0
- package/dist/components/library/heroui/Dropdown.js.map +1 -0
- package/dist/components/library/heroui/Input.js +10 -0
- package/dist/components/library/heroui/Input.js.map +1 -0
- package/dist/components/library/heroui/Kbd.js +12 -0
- package/dist/components/library/heroui/Kbd.js.map +1 -0
- package/dist/components/library/heroui/Meter.js +12 -0
- package/dist/components/library/heroui/Meter.js.map +1 -0
- package/dist/components/library/heroui/Modal.js +12 -0
- package/dist/components/library/heroui/Modal.js.map +1 -0
- package/dist/components/library/heroui/Pagination.js +12 -0
- package/dist/components/library/heroui/Pagination.js.map +1 -0
- package/dist/components/library/heroui/ProgressBar.js +12 -0
- package/dist/components/library/heroui/ProgressBar.js.map +1 -0
- package/dist/components/library/heroui/ProgressCircle.js +12 -0
- package/dist/components/library/heroui/ProgressCircle.js.map +1 -0
- package/dist/components/library/heroui/ScrollShadow.js +12 -0
- package/dist/components/library/heroui/ScrollShadow.js.map +1 -0
- package/dist/components/library/heroui/Select.js +12 -0
- package/dist/components/library/heroui/Select.js.map +1 -0
- package/dist/components/library/heroui/Separator.js +12 -0
- package/dist/components/library/heroui/Separator.js.map +1 -0
- package/dist/components/library/heroui/Skeleton.js +12 -0
- package/dist/components/library/heroui/Skeleton.js.map +1 -0
- package/dist/components/library/heroui/Tabs.js +12 -0
- package/dist/components/library/heroui/Tabs.js.map +1 -0
- package/dist/components/library/heroui/Toast.js +13 -0
- package/dist/components/library/heroui/Toast.js.map +1 -0
- package/dist/components/library/heroui/Toggle.js +12 -0
- package/dist/components/library/heroui/Toggle.js.map +1 -0
- package/dist/components/library/heroui/Tooltip.js +12 -0
- package/dist/components/library/heroui/Tooltip.js.map +1 -0
- package/dist/components/library/layout/PageContainer.js +9 -0
- package/dist/components/library/layout/PageContainer.js.map +1 -0
- package/dist/components/library/skeletons/CardSkeleton.js +29 -0
- package/dist/components/library/skeletons/CardSkeleton.js.map +1 -0
- package/dist/components/library/theme/AppThemeProvider.js +55 -0
- package/dist/components/library/theme/AppThemeProvider.js.map +1 -0
- package/dist/components/library/theme/tokens.js +55 -0
- package/dist/components/library/theme/tokens.js.map +1 -0
- package/dist/components/library/ui/Avatar.js +33 -0
- package/dist/components/library/ui/Avatar.js.map +1 -0
- package/dist/components/library/ui/Button.js +50 -0
- package/dist/components/library/ui/Button.js.map +1 -0
- package/dist/components/library/ui/Card.js +21 -0
- package/dist/components/library/ui/Card.js.map +1 -0
- package/dist/components/library/ui/Chip.js +31 -0
- package/dist/components/library/ui/Chip.js.map +1 -0
- package/dist/components/library/ui/Container.js +42 -0
- package/dist/components/library/ui/Container.js.map +1 -0
- package/dist/components/library/ui/EmptyState.js +27 -0
- package/dist/components/library/ui/EmptyState.js.map +1 -0
- package/dist/components/library/ui/Input.js +22 -0
- package/dist/components/library/ui/Input.js.map +1 -0
- package/dist/components/library/ui/Spinner.js +64 -0
- package/dist/components/library/ui/Spinner.js.map +1 -0
- package/dist/components/library/ui/Text.js +43 -0
- package/dist/components/library/ui/Text.js.map +1 -0
- package/dist/components/library/ui/Toggle.js +47 -0
- package/dist/components/library/ui/Toggle.js.map +1 -0
- package/dist/components/workspace/ComponentRegistry.js +231 -0
- package/dist/components/workspace/ComponentRegistry.js.map +1 -0
- package/dist/index.js +185 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/utils.js +9 -0
- package/dist/lib/utils.js.map +1 -0
- package/dist/node_modules/clsx/dist/clsx.js +17 -0
- package/dist/node_modules/clsx/dist/clsx.js.map +1 -0
- package/dist/node_modules/tailwind-merge/dist/bundle-mjs.js +2925 -0
- package/dist/node_modules/tailwind-merge/dist/bundle-mjs.js.map +1 -0
- package/package.json +81 -0
- package/scripts/get-graphql-schema.mjs +68 -0
- package/scripts/reset-command-center.sh +358 -0
- package/scripts/rewrite-e2e-assets.mjs +23 -0
- package/scripts/validate-dashboard.sh +290 -0
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# Implementation — Page
|
|
2
|
+
|
|
3
|
+
### Rules
|
|
4
|
+
|
|
5
|
+
1. **Edit the component that owns the UI, never output raw HTML** — When editing the home page or any page content, modify the actual `.tsx` file that renders the target. If the target is inside a child component (e.g. `<GlobalSearchInput />` in `Home.tsx`), edit the child's file (e.g. `GlobalSearchInput.tsx`), not the parent. Do not wrap the component with extra elements in the parent; go into the component and change its JSX. Do not paste or generate raw HTML.
|
|
6
|
+
2. **`routes.tsx` is the only route registry** — never add routes in `app.tsx` or inside page files.
|
|
7
|
+
3. **All pages are children of the AppLayout route** — do not create top-level routes that bypass the layout shell.
|
|
8
|
+
4. **Default export per page** — each page file has exactly one default-export component.
|
|
9
|
+
5. **Path aliases in all imports** — use `@/pages/...`, `@/components/...`; no deep relative paths.
|
|
10
|
+
6. **No inline styles** — Tailwind utility classes and design tokens only.
|
|
11
|
+
7. **Catch-all last** — `path: '*'` (NotFound) must always remain the last child in the layout route.
|
|
12
|
+
8. **Never modify `appLayout.tsx`** when adding a page — layout changes are a separate concern.
|
|
13
|
+
|
|
14
|
+
### Step 1 — Create the page file
|
|
15
|
+
|
|
16
|
+
Create `src/pages/MyPage.tsx` with a **default export** and the standard page container:
|
|
17
|
+
|
|
18
|
+
```tsx
|
|
19
|
+
export default function MyPage() {
|
|
20
|
+
return (
|
|
21
|
+
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
|
|
22
|
+
<h1 className="text-3xl font-bold text-foreground">My Page</h1>
|
|
23
|
+
<p className="mt-4 text-muted-foreground">Page content goes here.</p>
|
|
24
|
+
</div>
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Use shadcn components from `@/components/ui` for UI elements. All styling via Tailwind — no inline `style={{}}`.
|
|
30
|
+
|
|
31
|
+
### Step 2 — Register the route in `routes.tsx`
|
|
32
|
+
|
|
33
|
+
Open `src/routes.tsx`. Import the page and add it inside the layout route's `children` array:
|
|
34
|
+
|
|
35
|
+
```tsx
|
|
36
|
+
import MyPage from "@/pages/MyPage";
|
|
37
|
+
|
|
38
|
+
// Inside the layout route's children array (before the catch-all):
|
|
39
|
+
{
|
|
40
|
+
path: "my-page",
|
|
41
|
+
element: <MyPage />,
|
|
42
|
+
handle: { showInNavigation: true, label: "My Page" },
|
|
43
|
+
},
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
- `path` is a **relative segment** (e.g., `"contacts"`), not an absolute path.
|
|
47
|
+
- Include `handle: { showInNavigation: true, label: "Label" }` only if the page should appear in the navigation menu.
|
|
48
|
+
- The catch-all `path: '*'` must stay **last**.
|
|
49
|
+
|
|
50
|
+
### Step 3 — Apply an auth guard (if needed)
|
|
51
|
+
|
|
52
|
+
| Access type | Guard | Behavior |
|
|
53
|
+
| ---------------------------------- | ----------------------- | --------------------------------------- |
|
|
54
|
+
| Public | None | Direct child of layout |
|
|
55
|
+
| Authenticated only | `<PrivateRoute>` | Redirects to login if not authenticated |
|
|
56
|
+
| Unauthenticated only (e.g., login) | `<AuthenticationRoute>` | Redirects away if already authenticated |
|
|
57
|
+
|
|
58
|
+
Example — private page:
|
|
59
|
+
|
|
60
|
+
```tsx
|
|
61
|
+
import { PrivateRoute } from "@/components/auth/private-route";
|
|
62
|
+
|
|
63
|
+
{
|
|
64
|
+
path: "settings",
|
|
65
|
+
element: <PrivateRoute><SettingsPage /></PrivateRoute>,
|
|
66
|
+
handle: { showInNavigation: true, label: "Settings" },
|
|
67
|
+
},
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Use `ROUTES.*` constants from `@/utils/authenticationConfig` for auth-related paths — do not hardcode `/login`, `/profile`, etc.
|
|
71
|
+
|
|
72
|
+
### File Conventions — Page
|
|
73
|
+
|
|
74
|
+
| Concern | Location |
|
|
75
|
+
| ----------------- | ------------------------------------------------------ |
|
|
76
|
+
| Page component | `src/pages/<PageName>.tsx` (default export) |
|
|
77
|
+
| Route definition | `src/routes.tsx` only |
|
|
78
|
+
| Layout shell | `src/appLayout.tsx` — do not modify for page additions |
|
|
79
|
+
| Auth config paths | `ROUTES.*` from `@/utils/authenticationConfig` |
|
|
80
|
+
|
|
81
|
+
### State and Data
|
|
82
|
+
|
|
83
|
+
- **Local state:** `useState`, `useReducer`, `useRef` inside the page component
|
|
84
|
+
- **Shared or complex state:** extract to `src/hooks/` with a `use` prefix (e.g., `useContacts`)
|
|
85
|
+
- **Data fetching:** prefer GraphQL (`executeGraphQL`) or REST utilities in `src/api/`; place shared data logic in `src/hooks/`
|
|
86
|
+
- **Auth context:** `useAuth()` from `@/context/AuthContext` when current user is needed — only valid under `AuthProvider`
|
|
87
|
+
|
|
88
|
+
### Confirm — Page
|
|
89
|
+
|
|
90
|
+
- The page renders inside the app shell (header/nav visible)
|
|
91
|
+
- If `showInNavigation: true`, the link appears in the navigation menu
|
|
92
|
+
- No TypeScript errors; no broken imports; no missing exports
|
|
93
|
+
- Imports use path aliases (`@/`, not deep relative paths)
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: configuring-csp-trusted-sites
|
|
3
|
+
description: Creates Salesforce CSP Trusted Site metadata when adding external domains. Use when the user adds an external API, CDN, image host, font provider, map tile server, or any third-party URL that the web application needs to load resources from — or when a browser console shows a CSP violation error.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# CSP Trusted Sites
|
|
7
|
+
|
|
8
|
+
## When to Use
|
|
9
|
+
|
|
10
|
+
Use this skill whenever the application references a new external domain that is not already registered as a CSP Trusted Site. This includes:
|
|
11
|
+
|
|
12
|
+
- Adding images from a new CDN (Unsplash, Pexels, Cloudinary, etc.)
|
|
13
|
+
- Loading fonts from an external provider (Google Fonts, Adobe Fonts)
|
|
14
|
+
- Calling a third-party API (Open-Meteo, Nominatim, Mapbox, etc.)
|
|
15
|
+
- Loading map tiles from a tile server (OpenStreetMap, Mapbox)
|
|
16
|
+
- Embedding iframes from external services (YouTube, Vimeo)
|
|
17
|
+
- Loading external stylesheets or scripts
|
|
18
|
+
|
|
19
|
+
Salesforce enforces Content Security Policy (CSP) headers on all web applications. Any external domain not registered as a CSP Trusted Site will be blocked by the browser, causing images to not load, API calls to fail, or fonts to be missing.
|
|
20
|
+
|
|
21
|
+
**Reference:** [Salesforce CspTrustedSite Object Reference](https://developer.salesforce.com/docs/atlas.en-us.object_reference.meta/object_reference/sforce_api_objects_csptrustedsite.htm)
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Step 1 — Identify external domains
|
|
26
|
+
|
|
27
|
+
Scan the code for any URLs pointing to external domains. Common patterns:
|
|
28
|
+
|
|
29
|
+
- `fetch("https://api.example.com/...")` — API calls
|
|
30
|
+
- `<img src="https://images.example.com/..." />` — images
|
|
31
|
+
- `<link href="https://fonts.example.com/..." />` — stylesheets
|
|
32
|
+
- `url="https://tiles.example.com/{z}/{x}/{y}.png"` — map tiles
|
|
33
|
+
- `@import url("https://cdn.example.com/...")` — CSS imports
|
|
34
|
+
|
|
35
|
+
Extract the **origin** (scheme + host) from each URL. For example:
|
|
36
|
+
- `https://api.open-meteo.com/v1/forecast?lat=...` → `https://api.open-meteo.com`
|
|
37
|
+
- `https://images.unsplash.com/photo-123?w=800` → `https://images.unsplash.com`
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## Step 2 — Check existing CSP Trusted Sites
|
|
42
|
+
|
|
43
|
+
Before creating a new file, check if the domain already has a CSP Trusted Site:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
ls force-app/main/default/cspTrustedSites/
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
If the domain is already registered, no action is needed.
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## Step 3 — Determine the CSP directive(s)
|
|
54
|
+
|
|
55
|
+
Map the resource type to the correct CSP `isApplicableTo*Src` fields. Read `implementation/metadata-format.md` for the full reference.
|
|
56
|
+
|
|
57
|
+
Quick reference:
|
|
58
|
+
|
|
59
|
+
| Resource type | CSP directive field(s) to set `true` |
|
|
60
|
+
|--------------|--------------------------------------|
|
|
61
|
+
| Images (img, background-image) | `isApplicableToImgSrc` |
|
|
62
|
+
| API calls (fetch, XMLHttpRequest) | `isApplicableToConnectSrc` |
|
|
63
|
+
| Fonts (.woff, .woff2, .ttf) | `isApplicableToFontSrc` |
|
|
64
|
+
| Stylesheets (CSS) | `isApplicableToStyleSrc` |
|
|
65
|
+
| Video / audio | `isApplicableToMediaSrc` |
|
|
66
|
+
| Iframes | `isApplicableToFrameSrc` |
|
|
67
|
+
|
|
68
|
+
**Always also set `isApplicableToConnectSrc` to `true`** — most resources also require connect-src for preflight/redirect handling.
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## Step 4 — Create the metadata file
|
|
73
|
+
|
|
74
|
+
Read `implementation/metadata-format.md` and follow the instructions to create the `.cspTrustedSite-meta.xml` file.
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## Step 5 — Verify
|
|
79
|
+
|
|
80
|
+
1. Confirm the file is valid XML and matches the expected schema.
|
|
81
|
+
2. Confirm the file is placed in `force-app/main/default/cspTrustedSites/`.
|
|
82
|
+
3. Confirm only the necessary `isApplicableTo*Src` fields are set to `true`.
|
|
83
|
+
4. Run from the web app directory:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
cd force-app/main/default/webapplications/<appName> && npm run lint && npm run build
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
- **Lint:** MUST result in 0 errors.
|
|
90
|
+
- **Build:** MUST succeed.
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
# CSP Trusted Site Metadata — Implementation Guide
|
|
2
|
+
|
|
3
|
+
## File location
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
force-app/main/default/cspTrustedSites/{Name}.cspTrustedSite-meta.xml
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
The `cspTrustedSites/` directory must be a direct child of `force-app/main/default/`. Create it if it does not exist.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## File naming convention
|
|
14
|
+
|
|
15
|
+
The file name must match the `<fullName>` value inside the XML, with `.cspTrustedSite-meta.xml` appended.
|
|
16
|
+
|
|
17
|
+
| Domain | fullName | File name |
|
|
18
|
+
|--------|----------|-----------|
|
|
19
|
+
| `https://images.unsplash.com` | `Unsplash_Images` | `Unsplash_Images.cspTrustedSite-meta.xml` |
|
|
20
|
+
| `https://api.open-meteo.com` | `Open_Meteo_API` | `Open_Meteo_API.cspTrustedSite-meta.xml` |
|
|
21
|
+
| `https://tile.openstreetmap.org` | `OpenStreetMap_Tiles` | `OpenStreetMap_Tiles.cspTrustedSite-meta.xml` |
|
|
22
|
+
|
|
23
|
+
**Naming rules:**
|
|
24
|
+
- Use PascalCase with underscores separating words (e.g. `Google_Fonts_Static`)
|
|
25
|
+
- Name should describe the provider and resource type (e.g. `Pexels_Videos`, not just `Pexels`)
|
|
26
|
+
- Must be unique across the org
|
|
27
|
+
- Maximum 80 characters
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## XML template
|
|
32
|
+
|
|
33
|
+
```xml
|
|
34
|
+
<?xml version="1.0" encoding="UTF-8" ?>
|
|
35
|
+
<CspTrustedSite xmlns="http://soap.sforce.com/2006/04/metadata">
|
|
36
|
+
<fullName>{UNIQUE_NAME}</fullName>
|
|
37
|
+
<description>{DESCRIPTION}</description>
|
|
38
|
+
<endpointUrl>{HTTPS_ORIGIN}</endpointUrl>
|
|
39
|
+
<isActive>true</isActive>
|
|
40
|
+
<context>All</context>
|
|
41
|
+
<isApplicableToConnectSrc>{true|false}</isApplicableToConnectSrc>
|
|
42
|
+
<isApplicableToFontSrc>{true|false}</isApplicableToFontSrc>
|
|
43
|
+
<isApplicableToFrameSrc>{true|false}</isApplicableToFrameSrc>
|
|
44
|
+
<isApplicableToImgSrc>{true|false}</isApplicableToImgSrc>
|
|
45
|
+
<isApplicableToMediaSrc>{true|false}</isApplicableToMediaSrc>
|
|
46
|
+
<isApplicableToStyleSrc>{true|false}</isApplicableToStyleSrc>
|
|
47
|
+
</CspTrustedSite>
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## Field reference
|
|
53
|
+
|
|
54
|
+
| Field | Required | Description |
|
|
55
|
+
|-------|----------|-------------|
|
|
56
|
+
| `fullName` | Yes | Unique API name. Must match the file name (before `.cspTrustedSite-meta.xml`). |
|
|
57
|
+
| `description` | Yes | Human-readable purpose. Start with "Allow access to..." |
|
|
58
|
+
| `endpointUrl` | Yes | The external origin (scheme + host). Must start with `https://`. No trailing slash. No path. |
|
|
59
|
+
| `isActive` | Yes | Always `true` for new entries. Set `false` to disable without deleting. |
|
|
60
|
+
| `context` | Yes | `All` (applies to all contexts). Other values: `LEX` (Lightning Experience only), `Communities` (Experience Cloud only), `VisualForce`. Use `All` unless there is a specific reason to restrict. |
|
|
61
|
+
| `isApplicableToConnectSrc` | Yes | `true` if the domain is called via `fetch()`, `XMLHttpRequest`, or WebSocket. |
|
|
62
|
+
| `isApplicableToFontSrc` | Yes | `true` if the domain serves font files (`.woff`, `.woff2`, `.ttf`, `.otf`). |
|
|
63
|
+
| `isApplicableToFrameSrc` | Yes | `true` if the domain is loaded in an `<iframe>` or `<object>`. |
|
|
64
|
+
| `isApplicableToImgSrc` | Yes | `true` if the domain serves images (`<img>`, CSS `background-image`, `<svg>`). |
|
|
65
|
+
| `isApplicableToMediaSrc` | Yes | `true` if the domain serves audio or video (`<audio>`, `<video>`). |
|
|
66
|
+
| `isApplicableToStyleSrc` | Yes | `true` if the domain serves CSS stylesheets (`<link rel="stylesheet">`). |
|
|
67
|
+
|
|
68
|
+
**Reference:** [CspTrustedSite — Salesforce Object Reference](https://developer.salesforce.com/docs/atlas.en-us.object_reference.meta/object_reference/sforce_api_objects_csptrustedsite.htm)
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## CSP directive mapping
|
|
73
|
+
|
|
74
|
+
| CSP header directive | Metadata field | What it allows |
|
|
75
|
+
|---------------------|----------------|----------------|
|
|
76
|
+
| `connect-src` | `isApplicableToConnectSrc` | `fetch()`, `XMLHttpRequest`, WebSocket, `EventSource` |
|
|
77
|
+
| `font-src` | `isApplicableToFontSrc` | `@font-face` sources |
|
|
78
|
+
| `frame-src` | `isApplicableToFrameSrc` | `<iframe>`, `<frame>`, `<object>`, `<embed>` |
|
|
79
|
+
| `img-src` | `isApplicableToImgSrc` | `<img>`, `background-image`, `favicon`, `<picture>` |
|
|
80
|
+
| `media-src` | `isApplicableToMediaSrc` | `<audio>`, `<video>`, `<source>`, `<track>` |
|
|
81
|
+
| `style-src` | `isApplicableToStyleSrc` | `<link rel="stylesheet">`, `@import` in CSS |
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## Common external domains and their directives
|
|
86
|
+
|
|
87
|
+
Use this table as a quick reference when adding new domains:
|
|
88
|
+
|
|
89
|
+
| Domain | connect-src | font-src | frame-src | img-src | media-src | style-src |
|
|
90
|
+
|--------|:-----------:|:--------:|:---------:|:-------:|:---------:|:---------:|
|
|
91
|
+
| `https://images.unsplash.com` | true | false | false | true | false | false |
|
|
92
|
+
| `https://images.pexels.com` | true | false | false | true | false | false |
|
|
93
|
+
| `https://videos.pexels.com` | true | false | false | false | true | false |
|
|
94
|
+
| `https://fonts.googleapis.com` | true | false | false | false | false | true |
|
|
95
|
+
| `https://fonts.gstatic.com` | true | true | false | false | false | false |
|
|
96
|
+
| `https://avatars.githubusercontent.com` | true | false | false | true | false | false |
|
|
97
|
+
| `https://api.open-meteo.com` | true | false | false | false | false | false |
|
|
98
|
+
| `https://nominatim.openstreetmap.org` | true | false | false | false | false | false |
|
|
99
|
+
| `https://tile.openstreetmap.org` | true | false | false | true | false | false |
|
|
100
|
+
| `https://api.mapbox.com` | true | false | false | true | false | false |
|
|
101
|
+
| `https://cdn.jsdelivr.net` | true | false | false | false | false | true |
|
|
102
|
+
| `https://www.youtube.com` | false | false | true | true | false | false |
|
|
103
|
+
| `https://player.vimeo.com` | false | false | true | false | false | false |
|
|
104
|
+
| `https://res.cloudinary.com` | true | false | false | true | false | false |
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## Complete examples
|
|
109
|
+
|
|
110
|
+
### Image CDN (Unsplash)
|
|
111
|
+
|
|
112
|
+
```xml
|
|
113
|
+
<?xml version="1.0" encoding="UTF-8" ?>
|
|
114
|
+
<CspTrustedSite xmlns="http://soap.sforce.com/2006/04/metadata">
|
|
115
|
+
<fullName>Unsplash_Images</fullName>
|
|
116
|
+
<description>Allow access to Unsplash image content for static app media</description>
|
|
117
|
+
<endpointUrl>https://images.unsplash.com</endpointUrl>
|
|
118
|
+
<isActive>true</isActive>
|
|
119
|
+
<context>All</context>
|
|
120
|
+
<isApplicableToConnectSrc>true</isApplicableToConnectSrc>
|
|
121
|
+
<isApplicableToFontSrc>false</isApplicableToFontSrc>
|
|
122
|
+
<isApplicableToFrameSrc>false</isApplicableToFrameSrc>
|
|
123
|
+
<isApplicableToImgSrc>true</isApplicableToImgSrc>
|
|
124
|
+
<isApplicableToMediaSrc>false</isApplicableToMediaSrc>
|
|
125
|
+
<isApplicableToStyleSrc>false</isApplicableToStyleSrc>
|
|
126
|
+
</CspTrustedSite>
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### REST API (Open-Meteo weather)
|
|
130
|
+
|
|
131
|
+
```xml
|
|
132
|
+
<?xml version="1.0" encoding="UTF-8" ?>
|
|
133
|
+
<CspTrustedSite xmlns="http://soap.sforce.com/2006/04/metadata">
|
|
134
|
+
<fullName>Open_Meteo_API</fullName>
|
|
135
|
+
<description>Allow access to Open-Meteo weather forecast API</description>
|
|
136
|
+
<endpointUrl>https://api.open-meteo.com</endpointUrl>
|
|
137
|
+
<isActive>true</isActive>
|
|
138
|
+
<context>All</context>
|
|
139
|
+
<isApplicableToConnectSrc>true</isApplicableToConnectSrc>
|
|
140
|
+
<isApplicableToFontSrc>false</isApplicableToFontSrc>
|
|
141
|
+
<isApplicableToFrameSrc>false</isApplicableToFrameSrc>
|
|
142
|
+
<isApplicableToImgSrc>false</isApplicableToImgSrc>
|
|
143
|
+
<isApplicableToMediaSrc>false</isApplicableToMediaSrc>
|
|
144
|
+
<isApplicableToStyleSrc>false</isApplicableToStyleSrc>
|
|
145
|
+
</CspTrustedSite>
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### Font provider (Google Fonts — requires two entries)
|
|
149
|
+
|
|
150
|
+
Google Fonts needs two CSP entries because CSS is served from `fonts.googleapis.com` and font files from `fonts.gstatic.com`:
|
|
151
|
+
|
|
152
|
+
**Entry 1: Stylesheets**
|
|
153
|
+
```xml
|
|
154
|
+
<?xml version="1.0" encoding="UTF-8" ?>
|
|
155
|
+
<CspTrustedSite xmlns="http://soap.sforce.com/2006/04/metadata">
|
|
156
|
+
<fullName>Google_Fonts</fullName>
|
|
157
|
+
<description>Allow access to Google Fonts stylesheets for custom typography</description>
|
|
158
|
+
<endpointUrl>https://fonts.googleapis.com</endpointUrl>
|
|
159
|
+
<isActive>true</isActive>
|
|
160
|
+
<context>All</context>
|
|
161
|
+
<isApplicableToConnectSrc>true</isApplicableToConnectSrc>
|
|
162
|
+
<isApplicableToFontSrc>false</isApplicableToFontSrc>
|
|
163
|
+
<isApplicableToFrameSrc>false</isApplicableToFrameSrc>
|
|
164
|
+
<isApplicableToImgSrc>false</isApplicableToImgSrc>
|
|
165
|
+
<isApplicableToMediaSrc>false</isApplicableToMediaSrc>
|
|
166
|
+
<isApplicableToStyleSrc>true</isApplicableToStyleSrc>
|
|
167
|
+
</CspTrustedSite>
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
**Entry 2: Font files**
|
|
171
|
+
```xml
|
|
172
|
+
<?xml version="1.0" encoding="UTF-8" ?>
|
|
173
|
+
<CspTrustedSite xmlns="http://soap.sforce.com/2006/04/metadata">
|
|
174
|
+
<fullName>Google_Fonts_Static</fullName>
|
|
175
|
+
<description>Allow access to Google Fonts static files for font loading</description>
|
|
176
|
+
<endpointUrl>https://fonts.gstatic.com</endpointUrl>
|
|
177
|
+
<isActive>true</isActive>
|
|
178
|
+
<context>All</context>
|
|
179
|
+
<isApplicableToConnectSrc>true</isApplicableToConnectSrc>
|
|
180
|
+
<isApplicableToFontSrc>true</isApplicableToFontSrc>
|
|
181
|
+
<isApplicableToFrameSrc>false</isApplicableToFrameSrc>
|
|
182
|
+
<isApplicableToImgSrc>false</isApplicableToImgSrc>
|
|
183
|
+
<isApplicableToMediaSrc>false</isApplicableToMediaSrc>
|
|
184
|
+
<isApplicableToStyleSrc>false</isApplicableToStyleSrc>
|
|
185
|
+
</CspTrustedSite>
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### Map tiles (OpenStreetMap)
|
|
189
|
+
|
|
190
|
+
```xml
|
|
191
|
+
<?xml version="1.0" encoding="UTF-8" ?>
|
|
192
|
+
<CspTrustedSite xmlns="http://soap.sforce.com/2006/04/metadata">
|
|
193
|
+
<fullName>OpenStreetMap_Tiles</fullName>
|
|
194
|
+
<description>Allow access to OpenStreetMap tile images for map rendering</description>
|
|
195
|
+
<endpointUrl>https://tile.openstreetmap.org</endpointUrl>
|
|
196
|
+
<isActive>true</isActive>
|
|
197
|
+
<context>All</context>
|
|
198
|
+
<isApplicableToConnectSrc>true</isApplicableToConnectSrc>
|
|
199
|
+
<isApplicableToFontSrc>false</isApplicableToFontSrc>
|
|
200
|
+
<isApplicableToFrameSrc>false</isApplicableToFrameSrc>
|
|
201
|
+
<isApplicableToImgSrc>true</isApplicableToImgSrc>
|
|
202
|
+
<isApplicableToMediaSrc>false</isApplicableToMediaSrc>
|
|
203
|
+
<isApplicableToStyleSrc>false</isApplicableToStyleSrc>
|
|
204
|
+
</CspTrustedSite>
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### Geocoding API (Nominatim)
|
|
208
|
+
|
|
209
|
+
```xml
|
|
210
|
+
<?xml version="1.0" encoding="UTF-8" ?>
|
|
211
|
+
<CspTrustedSite xmlns="http://soap.sforce.com/2006/04/metadata">
|
|
212
|
+
<fullName>OpenStreetMap_Nominatim</fullName>
|
|
213
|
+
<description>Allow access to OpenStreetMap Nominatim geocoding API</description>
|
|
214
|
+
<endpointUrl>https://nominatim.openstreetmap.org</endpointUrl>
|
|
215
|
+
<isActive>true</isActive>
|
|
216
|
+
<context>All</context>
|
|
217
|
+
<isApplicableToConnectSrc>true</isApplicableToConnectSrc>
|
|
218
|
+
<isApplicableToFontSrc>false</isApplicableToFontSrc>
|
|
219
|
+
<isApplicableToFrameSrc>false</isApplicableToFrameSrc>
|
|
220
|
+
<isApplicableToImgSrc>false</isApplicableToImgSrc>
|
|
221
|
+
<isApplicableToMediaSrc>false</isApplicableToMediaSrc>
|
|
222
|
+
<isApplicableToStyleSrc>false</isApplicableToStyleSrc>
|
|
223
|
+
</CspTrustedSite>
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
---
|
|
227
|
+
|
|
228
|
+
## Endpoint URL rules
|
|
229
|
+
|
|
230
|
+
| Rule | Correct | Incorrect |
|
|
231
|
+
|------|---------|-----------|
|
|
232
|
+
| Must be HTTPS | `https://api.example.com` | `http://api.example.com` |
|
|
233
|
+
| No trailing slash | `https://api.example.com` | `https://api.example.com/` |
|
|
234
|
+
| No path | `https://api.example.com` | `https://api.example.com/v1/forecast` |
|
|
235
|
+
| No port (unless non-standard) | `https://api.example.com` | `https://api.example.com:443` |
|
|
236
|
+
| No wildcards | `https://api.example.com` | `https://*.example.com` |
|
|
237
|
+
|
|
238
|
+
Each subdomain needs its own entry. For example, `fonts.googleapis.com` and `fonts.gstatic.com` are separate entries.
|
|
239
|
+
|
|
240
|
+
---
|
|
241
|
+
|
|
242
|
+
## When a service requires multiple domains
|
|
243
|
+
|
|
244
|
+
Some services split resources across multiple subdomains. Create one CSP Trusted Site per domain:
|
|
245
|
+
|
|
246
|
+
| Service | Domains needed |
|
|
247
|
+
|---------|---------------|
|
|
248
|
+
| Google Fonts | `fonts.googleapis.com` (CSS) + `fonts.gstatic.com` (font files) |
|
|
249
|
+
| Mapbox | `api.mapbox.com` (tiles/API) + `events.mapbox.com` (telemetry) |
|
|
250
|
+
| YouTube embed | `www.youtube.com` (iframe) + `i.ytimg.com` (thumbnails) |
|
|
251
|
+
| Cloudflare CDN | `cdnjs.cloudflare.com` (scripts/CSS) |
|
|
252
|
+
|
|
253
|
+
---
|
|
254
|
+
|
|
255
|
+
## Troubleshooting CSP violations
|
|
256
|
+
|
|
257
|
+
If the browser console shows a CSP error like:
|
|
258
|
+
|
|
259
|
+
```
|
|
260
|
+
Refused to load the image 'https://example.com/image.png' because it violates
|
|
261
|
+
the following Content Security Policy directive: "img-src 'self' ..."
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
1. Extract the **blocked origin** from the URL (e.g. `https://example.com`).
|
|
265
|
+
2. Identify the **directive** from the error message (e.g. `img-src` → `isApplicableToImgSrc`).
|
|
266
|
+
3. Check if a CSP Trusted Site already exists for that origin.
|
|
267
|
+
4. If not, create one using this skill.
|
|
268
|
+
5. Deploy the metadata and refresh the page.
|
|
269
|
+
|
|
270
|
+
---
|
|
271
|
+
|
|
272
|
+
## Common mistakes
|
|
273
|
+
|
|
274
|
+
| Mistake | Fix |
|
|
275
|
+
|---------|-----|
|
|
276
|
+
| Including a path in `endpointUrl` | Use only the origin: `https://api.example.com` |
|
|
277
|
+
| Adding trailing slash | Remove it: `https://api.example.com` not `https://api.example.com/` |
|
|
278
|
+
| Using HTTP instead of HTTPS | Salesforce requires HTTPS. If the service only supports HTTP, it cannot be added. |
|
|
279
|
+
| Forgetting `isApplicableToConnectSrc` | Most resources also need connect-src for redirects/preflight. Set to `true` by default. |
|
|
280
|
+
| One entry for multiple subdomains | Each subdomain needs its own file (e.g. `api.example.com` and `cdn.example.com` are separate) |
|
|
281
|
+
| File name doesn't match `fullName` | They must be identical (excluding the `.cspTrustedSite-meta.xml` extension) |
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: configuring-webapp-metadata
|
|
3
|
+
description: Use this skill when configuring web application metadata structure, webapplication.json, or bundle organization. Covers WebApplication bundle layout, meta XML, build output directory, and webapplication.json settings.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# WebApplication Requirements
|
|
7
|
+
|
|
8
|
+
## Bundle Rules
|
|
9
|
+
- A WebApplication bundle must live under `webapplications/<AppName>/`
|
|
10
|
+
- The bundle must contain `<AppName>.webapplication-meta.xml`
|
|
11
|
+
- The metadata filename must exactly match the folder name
|
|
12
|
+
- A build output directory must exist and contain at least one file
|
|
13
|
+
- Default build output directory: `dist/`
|
|
14
|
+
- If `webapplication.json.outputDir` is set, it overrides `dist/`
|
|
15
|
+
|
|
16
|
+
Valid example:
|
|
17
|
+
```text
|
|
18
|
+
webapplications/
|
|
19
|
+
MyApp/
|
|
20
|
+
MyApp.webapplication-meta.xml
|
|
21
|
+
webapplication.json
|
|
22
|
+
dist/
|
|
23
|
+
index.html
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Metadata XML
|
|
27
|
+
Required fields:
|
|
28
|
+
- `masterLabel`
|
|
29
|
+
- `version` (max 20 chars)
|
|
30
|
+
- `isActive` (boolean)
|
|
31
|
+
|
|
32
|
+
Optional fields:
|
|
33
|
+
- `description` (max 255 chars)
|
|
34
|
+
|
|
35
|
+
## webapplication.json
|
|
36
|
+
`webapplication.json` is optional.
|
|
37
|
+
|
|
38
|
+
Allowed top-level keys only:
|
|
39
|
+
- `outputDir`
|
|
40
|
+
- `routing`
|
|
41
|
+
- `headers`
|
|
42
|
+
|
|
43
|
+
### File Constraints
|
|
44
|
+
- Must be valid UTF-8 JSON
|
|
45
|
+
- Max size: 100 KB
|
|
46
|
+
- Root must be a non-empty object
|
|
47
|
+
- Never allow `{}`, arrays, or primitives as the root
|
|
48
|
+
|
|
49
|
+
### Path Safety
|
|
50
|
+
Applies to:
|
|
51
|
+
- `outputDir`
|
|
52
|
+
- `routing.fallback`
|
|
53
|
+
|
|
54
|
+
Reject:
|
|
55
|
+
- backslashes
|
|
56
|
+
- leading `/` or `\`
|
|
57
|
+
- `..` segments
|
|
58
|
+
- null or control characters
|
|
59
|
+
- globs: `*`, `?`, `**`
|
|
60
|
+
- `%`
|
|
61
|
+
|
|
62
|
+
All resolved paths must stay within the application bundle.
|
|
63
|
+
|
|
64
|
+
### outputDir
|
|
65
|
+
- Must be a non-empty string
|
|
66
|
+
- Must reference a subdirectory only
|
|
67
|
+
- Reject `.` and `./`
|
|
68
|
+
- The directory must exist in the bundle
|
|
69
|
+
- The directory must contain at least one file
|
|
70
|
+
|
|
71
|
+
### routing
|
|
72
|
+
- If present, must be a non-empty object
|
|
73
|
+
- Allowed keys only:
|
|
74
|
+
- `rewrites`
|
|
75
|
+
- `redirects`
|
|
76
|
+
- `fallback`
|
|
77
|
+
- `trailingSlash`
|
|
78
|
+
- `fileBasedRouting`
|
|
79
|
+
|
|
80
|
+
#### routing.trailingSlash
|
|
81
|
+
- Must be one of: `"always"`, `"never"`, `"auto"`
|
|
82
|
+
|
|
83
|
+
#### routing.fileBasedRouting
|
|
84
|
+
- Must be a boolean
|
|
85
|
+
|
|
86
|
+
#### routing.fallback
|
|
87
|
+
- Must be a non-empty string
|
|
88
|
+
- Must satisfy Path Safety rules
|
|
89
|
+
- Target file must exist
|
|
90
|
+
|
|
91
|
+
#### routing.rewrites
|
|
92
|
+
- Must be a non-empty array
|
|
93
|
+
- Each item must be a non-empty object
|
|
94
|
+
- Allowed keys: `route`, `rewrite`
|
|
95
|
+
- `rewrite` must be a non-empty string
|
|
96
|
+
- `route`, if present, must be a non-empty string
|
|
97
|
+
|
|
98
|
+
Example:
|
|
99
|
+
```json
|
|
100
|
+
{
|
|
101
|
+
"routing": {
|
|
102
|
+
"rewrites": [
|
|
103
|
+
{ "route": "/app/:path*", "rewrite": "/index.html" }
|
|
104
|
+
]
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
#### routing.redirects
|
|
110
|
+
- Must be a non-empty array
|
|
111
|
+
- Each item must be a non-empty object
|
|
112
|
+
- Allowed keys: `route`, `redirect`, `statusCode`
|
|
113
|
+
- `redirect` must be a non-empty string
|
|
114
|
+
- `route`, if present, must be a non-empty string
|
|
115
|
+
- `statusCode`, if present, must be one of: `301`, `302`, `307`, `308`
|
|
116
|
+
|
|
117
|
+
Example:
|
|
118
|
+
```json
|
|
119
|
+
{
|
|
120
|
+
"routing": {
|
|
121
|
+
"redirects": [
|
|
122
|
+
{ "route": "/old-page", "redirect": "/new-page", "statusCode": 301 }
|
|
123
|
+
]
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### headers
|
|
129
|
+
- If present, must be a non-empty array
|
|
130
|
+
- Each item must be a non-empty object
|
|
131
|
+
- Allowed keys: `source`, `headers`
|
|
132
|
+
- `headers` must be a non-empty array
|
|
133
|
+
|
|
134
|
+
Each header entry must contain:
|
|
135
|
+
- `key`: non-empty string
|
|
136
|
+
- `value`: non-empty string
|
|
137
|
+
|
|
138
|
+
Example:
|
|
139
|
+
```json
|
|
140
|
+
{
|
|
141
|
+
"headers": [
|
|
142
|
+
{
|
|
143
|
+
"source": "/assets/**",
|
|
144
|
+
"headers": [
|
|
145
|
+
{ "key": "Cache-Control", "value": "public, max-age=31536000, immutable" }
|
|
146
|
+
]
|
|
147
|
+
}
|
|
148
|
+
]
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## Never Suggest
|
|
153
|
+
- `{}` as the JSON root
|
|
154
|
+
- `"routing": {}`
|
|
155
|
+
- empty arrays
|
|
156
|
+
- empty array items such as `[{}]`
|
|
157
|
+
- `"outputDir": "."`
|
|
158
|
+
- `"outputDir": "./"`
|