@salesforce/webapp-template-base-sfdx-project-experimental 1.103.2
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/webapp-csp-trusted-sites/SKILL.md +90 -0
- package/.a4drules/skills/webapp-csp-trusted-sites/implementation/metadata-format.md +281 -0
- package/.a4drules/skills/webapp-features/SKILL.md +210 -0
- package/.a4drules/skills/webapp-react/SKILL.md +80 -0
- package/.a4drules/skills/webapp-react/implementation/component.md +78 -0
- package/.a4drules/skills/webapp-react/implementation/header-footer.md +132 -0
- package/.a4drules/skills/webapp-react/implementation/page.md +93 -0
- package/.a4drules/skills/webapp-react-data-visualization/SKILL.md +72 -0
- package/.a4drules/skills/webapp-react-data-visualization/implementation/dashboard-layout.md +189 -0
- package/.a4drules/skills/webapp-react-data-visualization/implementation/donut-chart.md +181 -0
- package/.a4drules/skills/webapp-react-data-visualization/implementation/stat-card.md +150 -0
- package/.a4drules/skills/webapp-react-interactive-map/SKILL.md +92 -0
- package/.a4drules/skills/webapp-react-interactive-map/implementation/geocoding.md +245 -0
- package/.a4drules/skills/webapp-react-interactive-map/implementation/leaflet-map.md +279 -0
- package/.a4drules/skills/webapp-react-weather-widget/SKILL.md +65 -0
- package/.a4drules/skills/webapp-react-weather-widget/implementation/weather-hook.md +258 -0
- package/.a4drules/skills/webapp-react-weather-widget/implementation/weather-ui.md +216 -0
- package/.a4drules/skills/webapp-ui-ux/SKILL.md +271 -0
- package/.a4drules/skills/webapp-ui-ux/data/charts.csv +26 -0
- package/.a4drules/skills/webapp-ui-ux/data/colors.csv +97 -0
- package/.a4drules/skills/webapp-ui-ux/data/icons.csv +101 -0
- package/.a4drules/skills/webapp-ui-ux/data/landing.csv +31 -0
- package/.a4drules/skills/webapp-ui-ux/data/products.csv +97 -0
- package/.a4drules/skills/webapp-ui-ux/data/react-performance.csv +45 -0
- package/.a4drules/skills/webapp-ui-ux/data/stacks/html-tailwind.csv +56 -0
- package/.a4drules/skills/webapp-ui-ux/data/stacks/react.csv +54 -0
- package/.a4drules/skills/webapp-ui-ux/data/stacks/shadcn.csv +61 -0
- package/.a4drules/skills/webapp-ui-ux/data/styles.csv +68 -0
- package/.a4drules/skills/webapp-ui-ux/data/typography.csv +58 -0
- package/.a4drules/skills/webapp-ui-ux/data/ui-reasoning.csv +101 -0
- package/.a4drules/skills/webapp-ui-ux/data/ux-guidelines.csv +100 -0
- package/.a4drules/skills/webapp-ui-ux/data/web-interface.csv +31 -0
- package/.a4drules/skills/webapp-ui-ux/scripts/core.js +255 -0
- package/.a4drules/skills/webapp-ui-ux/scripts/design_system.js +861 -0
- package/.a4drules/skills/webapp-ui-ux/scripts/search.js +98 -0
- package/.a4drules/skills/webapp-unsplash-images/SKILL.md +71 -0
- package/.a4drules/skills/webapp-unsplash-images/implementation/usage.md +159 -0
- package/.a4drules/webapp-cli-commands.md +88 -0
- package/.a4drules/webapp-react-code-quality.md +136 -0
- package/.a4drules/webapp-react-typescript.md +205 -0
- package/.a4drules/webapp-react.md +202 -0
- package/.a4drules/webapp-skills-first.md +26 -0
- package/.a4drules/webapp-webapplication.md +159 -0
- package/.a4drules/webapp.md +98 -0
- package/.forceignore +15 -0
- package/.husky/pre-commit +4 -0
- package/.prettierignore +11 -0
- package/.prettierrc +17 -0
- package/AGENT.md +81 -0
- package/CHANGELOG.md +1577 -0
- package/LICENSE.txt +82 -0
- package/README.md +18 -0
- package/config/project-scratch-def.json +13 -0
- package/jest.config.js +6 -0
- package/package.json +39 -0
- package/scripts/apex/hello.apex +10 -0
- package/scripts/prepare-import-unique-fields.js +122 -0
- package/scripts/setup-cli.mjs +533 -0
- package/scripts/soql/account.soql +6 -0
- package/sfdx-project.json +12 -0
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { CSV_CONFIG, AVAILABLE_STACKS, MAX_RESULTS, search, searchStack } from './core.js';
|
|
4
|
+
import { generateDesignSystem } from './design_system.js';
|
|
5
|
+
|
|
6
|
+
// ============ ARG PARSER ============
|
|
7
|
+
|
|
8
|
+
function parseArgs(argv) {
|
|
9
|
+
const args = { query: null, maxResults: MAX_RESULTS, format: 'ascii' };
|
|
10
|
+
let i = 2;
|
|
11
|
+
while (i < argv.length) {
|
|
12
|
+
const a = argv[i];
|
|
13
|
+
if (a === '--domain' || a === '-d') { args.domain = argv[++i]; }
|
|
14
|
+
else if (a === '--stack' || a === '-s') { args.stack = argv[++i]; }
|
|
15
|
+
else if (a === '--max-results' || a === '-n') { args.maxResults = parseInt(argv[++i], 10); }
|
|
16
|
+
else if (a === '--json') { args.json = true; }
|
|
17
|
+
else if (a === '--design-system' || a === '-ds') { args.designSystem = true; }
|
|
18
|
+
else if (a === '--project-name' || a === '-p') { args.projectName = argv[++i]; }
|
|
19
|
+
else if (a === '--format' || a === '-f') { args.format = argv[++i]; }
|
|
20
|
+
else if (a === '--persist') { args.persist = true; }
|
|
21
|
+
else if (a === '--page') { args.page = argv[++i]; }
|
|
22
|
+
else if (a === '--output-dir' || a === '-o') { args.outputDir = argv[++i]; }
|
|
23
|
+
else if (!a.startsWith('-') && !args.query) { args.query = a; }
|
|
24
|
+
i++;
|
|
25
|
+
}
|
|
26
|
+
return args;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// ============ FORMATTER ============
|
|
30
|
+
|
|
31
|
+
function formatOutput(result) {
|
|
32
|
+
if (result.error) return `Error: ${result.error}`;
|
|
33
|
+
|
|
34
|
+
const lines = [];
|
|
35
|
+
if (result.stack) {
|
|
36
|
+
lines.push('## UI Pro Max Stack Guidelines');
|
|
37
|
+
lines.push(`**Stack:** ${result.stack} | **Query:** ${result.query}`);
|
|
38
|
+
} else {
|
|
39
|
+
lines.push('## UI Pro Max Search Results');
|
|
40
|
+
lines.push(`**Domain:** ${result.domain} | **Query:** ${result.query}`);
|
|
41
|
+
}
|
|
42
|
+
lines.push(`**Source:** ${result.file} | **Found:** ${result.count} results\n`);
|
|
43
|
+
|
|
44
|
+
(result.results || []).forEach((row, i) => {
|
|
45
|
+
lines.push(`### Result ${i + 1}`);
|
|
46
|
+
for (const [key, value] of Object.entries(row)) {
|
|
47
|
+
const v = String(value);
|
|
48
|
+
lines.push(`- **${key}:** ${v.length > 300 ? v.slice(0, 300) + '...' : v}`);
|
|
49
|
+
}
|
|
50
|
+
lines.push('');
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
return lines.join('\n');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// ============ MAIN ============
|
|
57
|
+
|
|
58
|
+
const args = parseArgs(process.argv);
|
|
59
|
+
|
|
60
|
+
if (!args.query) {
|
|
61
|
+
console.error('Usage: node search.js "<query>" [--domain <d>] [--stack <s>] [--design-system] [-p "Name"]');
|
|
62
|
+
console.error(`Domains: ${Object.keys(CSV_CONFIG).join(', ')}`);
|
|
63
|
+
console.error(`Stacks: ${AVAILABLE_STACKS.join(', ')}`);
|
|
64
|
+
process.exit(1);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (args.designSystem) {
|
|
68
|
+
const result = generateDesignSystem(
|
|
69
|
+
args.query,
|
|
70
|
+
args.projectName,
|
|
71
|
+
args.format,
|
|
72
|
+
!!args.persist,
|
|
73
|
+
args.page || null,
|
|
74
|
+
args.outputDir || null
|
|
75
|
+
);
|
|
76
|
+
console.log(result);
|
|
77
|
+
|
|
78
|
+
if (args.persist) {
|
|
79
|
+
const slug = args.projectName ? args.projectName.toLowerCase().replace(/\s+/g, '-') : 'default';
|
|
80
|
+
console.log('\n' + '='.repeat(60));
|
|
81
|
+
console.log(`✅ Design system persisted to design-system/${slug}/`);
|
|
82
|
+
console.log(` 📄 design-system/${slug}/MASTER.md (Global Source of Truth)`);
|
|
83
|
+
if (args.page) {
|
|
84
|
+
const pageSlug = args.page.toLowerCase().replace(/\s+/g, '-');
|
|
85
|
+
console.log(` 📄 design-system/${slug}/pages/${pageSlug}.md (Page Overrides)`);
|
|
86
|
+
}
|
|
87
|
+
console.log('');
|
|
88
|
+
console.log(`📖 Usage: When building a page, check design-system/${slug}/pages/[page].md first.`);
|
|
89
|
+
console.log(` If exists, its rules override MASTER.md. Otherwise, use MASTER.md.`);
|
|
90
|
+
console.log('='.repeat(60));
|
|
91
|
+
}
|
|
92
|
+
} else if (args.stack) {
|
|
93
|
+
const result = searchStack(args.query, args.stack, args.maxResults);
|
|
94
|
+
console.log(args.json ? JSON.stringify(result, null, 2) : formatOutput(result));
|
|
95
|
+
} else {
|
|
96
|
+
const result = search(args.query, args.domain, args.maxResults);
|
|
97
|
+
console.log(args.json ? JSON.stringify(result, null, 2) : formatOutput(result));
|
|
98
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: webapp-unsplash-images
|
|
3
|
+
description: Adds high-quality Unsplash images to React pages. Use when the user asks to add a hero image, background image, placeholder image, stock photo, decorative image, or any Unsplash-sourced imagery to the web application.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Unsplash Images
|
|
7
|
+
|
|
8
|
+
## When to Use
|
|
9
|
+
|
|
10
|
+
Use this skill when:
|
|
11
|
+
- Adding hero banners, section backgrounds, or decorative images
|
|
12
|
+
- The user asks for stock photography or placeholder images
|
|
13
|
+
- A page needs visual appeal without custom assets
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Step 1 — Determine image purpose
|
|
18
|
+
|
|
19
|
+
Identify what the image is for:
|
|
20
|
+
|
|
21
|
+
- **Hero / banner** — full-width background behind text or a CTA
|
|
22
|
+
- **Section accent** — smaller image alongside content (stats, testimonials, features)
|
|
23
|
+
- **Card thumbnail** — image inside a card component
|
|
24
|
+
- **Background texture** — subtle decorative background
|
|
25
|
+
|
|
26
|
+
If unclear, ask:
|
|
27
|
+
|
|
28
|
+
> "Where should the image appear — as a hero banner, a section accent, or a card thumbnail?"
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## Step 2 — Select the right Unsplash URL format
|
|
33
|
+
|
|
34
|
+
Read `implementation/usage.md` for the full reference. Key rules:
|
|
35
|
+
|
|
36
|
+
1. **Always use `images.unsplash.com/photo-{id}` format** with explicit `w` and `q` parameters.
|
|
37
|
+
2. **Never use `source.unsplash.com`** — it is deprecated and returns 404s.
|
|
38
|
+
3. **Pin every image by its photo ID** — never rely on random or search endpoints.
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## Step 3 — Validate image URLs
|
|
43
|
+
|
|
44
|
+
Before using any Unsplash URL in code:
|
|
45
|
+
|
|
46
|
+
1. Open the URL in a browser or fetch it to confirm it returns a 200 status and a valid image.
|
|
47
|
+
2. If the URL returns a 404, redirect loop, or broken image, **discard it** and pick a different photo ID.
|
|
48
|
+
3. Never ship a URL you have not personally verified.
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## Step 4 — Implementation
|
|
53
|
+
|
|
54
|
+
Read `implementation/usage.md` and follow the instructions there.
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## Verification
|
|
59
|
+
|
|
60
|
+
Before completing:
|
|
61
|
+
|
|
62
|
+
1. Confirm every Unsplash URL loads a valid image (no 404, no redirect loops).
|
|
63
|
+
2. Confirm `alt` text is set appropriately (empty `alt=""` for decorative, descriptive for meaningful).
|
|
64
|
+
3. Run from the web app directory:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
cd force-app/main/default/webapplications/<appName> && npm run lint && npm run build
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
- **Lint:** MUST result in 0 errors.
|
|
71
|
+
- **Build:** MUST succeed.
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# Unsplash Images — Implementation Guide
|
|
2
|
+
|
|
3
|
+
## URL Format
|
|
4
|
+
|
|
5
|
+
Always use the direct `images.unsplash.com` format with explicit sizing:
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
https://images.unsplash.com/photo-{PHOTO_ID}?w={WIDTH}&q={QUALITY}
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
| Parameter | Purpose | Recommended values |
|
|
12
|
+
|-----------|---------|--------------------|
|
|
13
|
+
| `w` | Pixel width served by the CDN | `600` card, `800` section, `1200` hero, `1920` full-bleed |
|
|
14
|
+
| `q` | JPEG quality 1–100 | `80`–`85` (good balance of quality vs size) |
|
|
15
|
+
|
|
16
|
+
### Deprecated / broken formats — do NOT use
|
|
17
|
+
|
|
18
|
+
| Format | Why it fails |
|
|
19
|
+
|--------|-------------|
|
|
20
|
+
| `source.unsplash.com/random` | Deprecated; returns 404 |
|
|
21
|
+
| `source.unsplash.com/{WIDTH}x{HEIGHT}` | Deprecated; returns 404 |
|
|
22
|
+
| `source.unsplash.com/featured/?{query}` | Deprecated; returns 404 |
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## How to find a valid photo ID
|
|
27
|
+
|
|
28
|
+
1. Go to [unsplash.com](https://unsplash.com) and search for the subject (e.g. "modern apartment").
|
|
29
|
+
2. Open a photo. The URL will look like `unsplash.com/photos/{slug}-{PHOTO_ID}` or `unsplash.com/photos/{PHOTO_ID}`.
|
|
30
|
+
3. The `PHOTO_ID` is the last hyphen-separated segment (e.g. `photo-1600596542815-ffad4c1539a9`).
|
|
31
|
+
4. Build your URL: `https://images.unsplash.com/photo-1600596542815-ffad4c1539a9?w=1200&q=85`
|
|
32
|
+
5. **Test the URL** in a browser before committing.
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Declaring image constants
|
|
37
|
+
|
|
38
|
+
Define all Unsplash URLs as named constants at the top of the file. Never inline URLs in JSX.
|
|
39
|
+
|
|
40
|
+
```tsx
|
|
41
|
+
const HERO_IMAGE = "https://images.unsplash.com/photo-1600596542815-ffad4c1539a9?w=1200&q=85";
|
|
42
|
+
const SECTION_IMAGE = "https://images.unsplash.com/photo-1514565131-fce0801e5785?w=800&q=85";
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Rendering images in JSX
|
|
48
|
+
|
|
49
|
+
### Hero / banner (decorative — empty alt)
|
|
50
|
+
|
|
51
|
+
```tsx
|
|
52
|
+
<div className="relative w-full overflow-hidden rounded-2xl">
|
|
53
|
+
<div className="relative aspect-[21/9] min-h-[280px] w-full md:aspect-[3/1]">
|
|
54
|
+
<img
|
|
55
|
+
src={HERO_IMAGE}
|
|
56
|
+
alt=""
|
|
57
|
+
className="h-full w-full object-cover"
|
|
58
|
+
loading="eager"
|
|
59
|
+
fetchPriority="high"
|
|
60
|
+
/>
|
|
61
|
+
<div className="absolute inset-0 bg-black/40" />
|
|
62
|
+
<div className="absolute inset-0 flex flex-col items-center justify-center px-4">
|
|
63
|
+
{/* overlay content */}
|
|
64
|
+
</div>
|
|
65
|
+
</div>
|
|
66
|
+
</div>
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Key points:
|
|
70
|
+
- `alt=""` for decorative images (screen readers skip them).
|
|
71
|
+
- `loading="eager"` and `fetchPriority="high"` for above-the-fold heroes.
|
|
72
|
+
- `object-cover` prevents stretching.
|
|
73
|
+
- Semi-transparent overlay (`bg-black/40`) ensures text readability.
|
|
74
|
+
|
|
75
|
+
### Section accent (meaningful — descriptive alt)
|
|
76
|
+
|
|
77
|
+
```tsx
|
|
78
|
+
<div className="relative min-h-[240px] overflow-hidden rounded-2xl">
|
|
79
|
+
<img src={SECTION_IMAGE} alt="City skyline at sunset" className="h-full w-full object-cover" />
|
|
80
|
+
<div className="absolute inset-0 bg-gradient-to-r from-black/20 to-transparent" />
|
|
81
|
+
</div>
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Card thumbnail
|
|
85
|
+
|
|
86
|
+
```tsx
|
|
87
|
+
<div className="aspect-[4/3] overflow-hidden bg-muted">
|
|
88
|
+
<img
|
|
89
|
+
src={imageUrl}
|
|
90
|
+
alt=""
|
|
91
|
+
className="h-full w-full object-cover transition-transform hover:scale-105"
|
|
92
|
+
loading="lazy"
|
|
93
|
+
/>
|
|
94
|
+
</div>
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Key points:
|
|
98
|
+
- `loading="lazy"` for below-the-fold images.
|
|
99
|
+
- `hover:scale-105` subtle zoom on hover.
|
|
100
|
+
- `bg-muted` fallback color while loading.
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## Fallback when no image is available
|
|
105
|
+
|
|
106
|
+
Always provide a placeholder when the image URL may be null:
|
|
107
|
+
|
|
108
|
+
```tsx
|
|
109
|
+
{imageUrl ? (
|
|
110
|
+
<img src={imageUrl} alt="" className="h-full w-full object-cover" />
|
|
111
|
+
) : (
|
|
112
|
+
<div className="flex h-full items-center justify-center text-muted-foreground">
|
|
113
|
+
No image
|
|
114
|
+
</div>
|
|
115
|
+
)}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## Content Security Policy (CSP)
|
|
121
|
+
|
|
122
|
+
If the application enforces CSP headers, add `images.unsplash.com` to `img-src`:
|
|
123
|
+
|
|
124
|
+
```
|
|
125
|
+
img-src 'self' https://images.unsplash.com;
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Other commonly needed origins for stock images:
|
|
129
|
+
|
|
130
|
+
| Origin | Purpose |
|
|
131
|
+
|--------|---------|
|
|
132
|
+
| `images.unsplash.com` | Unsplash photos |
|
|
133
|
+
| `images.pexels.com` | Pexels photos |
|
|
134
|
+
| `fonts.googleapis.com` | Google Fonts CSS |
|
|
135
|
+
| `fonts.gstatic.com` | Google Fonts files |
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## Accessibility checklist
|
|
140
|
+
|
|
141
|
+
- [ ] Decorative images have `alt=""`
|
|
142
|
+
- [ ] Meaningful images have descriptive `alt` text
|
|
143
|
+
- [ ] Hero images use `loading="eager"` and `fetchPriority="high"`
|
|
144
|
+
- [ ] Below-fold images use `loading="lazy"`
|
|
145
|
+
- [ ] Text over images has sufficient contrast (use overlay like `bg-black/40`)
|
|
146
|
+
- [ ] All URLs verified to return a valid image (no 404s)
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## Common mistakes
|
|
151
|
+
|
|
152
|
+
| Mistake | Fix |
|
|
153
|
+
|---------|-----|
|
|
154
|
+
| Using `source.unsplash.com` | Replace with `images.unsplash.com/photo-{id}?w=…&q=…` |
|
|
155
|
+
| Hardcoding an unverified URL | Open URL in browser first; replace if broken |
|
|
156
|
+
| Missing `w` parameter | Always set width — CDN returns full-res (5000px+) otherwise |
|
|
157
|
+
| `loading="lazy"` on hero | Use `loading="eager"` for above-the-fold images |
|
|
158
|
+
| No fallback for nullable URLs | Wrap in conditional with placeholder div |
|
|
159
|
+
| Inline URLs in JSX | Extract to named constants at file top |
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: CLI command generation rules — no node -e one-liners; safe quoting and scripts
|
|
3
|
+
paths:
|
|
4
|
+
- "**/webapplications/**/*"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# A4D Enforcement: CLI Command Generation & No `node -e` One-Liners
|
|
8
|
+
|
|
9
|
+
When **generating any CLI/shell commands** (for the user or for automation), follow the rules below. Default shell is **Zsh** (macOS); commands must work there without Bash-only syntax.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## 1. Never Use Complex `node -e` One-Liners
|
|
14
|
+
|
|
15
|
+
**Forbidden:** `node -e` (or `node -p`, `node --eval`) for file manipulation, string replacement, reading/writing configs, or multi-line logic.
|
|
16
|
+
|
|
17
|
+
**Why:** In Zsh, `node -e '...'` **silently breaks** due to:
|
|
18
|
+
- **`!` (history expansion):** Zsh expands `!` in double-quoted strings → `event not found` or wrong output.
|
|
19
|
+
- **Backticks:** `` ` `` is command substitution; the shell runs it before Node sees the string.
|
|
20
|
+
- **Nested quoting:** Escaping differs between Bash and Zsh; multi-line JS in one string is fragile.
|
|
21
|
+
|
|
22
|
+
**Allowed:** Trivial one-line only if it has **no** backticks, no `!`, no nested quotes, no multi-line, no `fs` usage. Example: `node -e "console.log(1+1)"`. If in doubt, **use a script file**.
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## 2. How To Generate CLI Commands Correctly
|
|
27
|
+
|
|
28
|
+
### Run Node scripts by path, not inline code
|
|
29
|
+
|
|
30
|
+
- **Do:** `node scripts/setup-cli.mjs --target-org myorg`
|
|
31
|
+
- **Do:** `node path/to/script.mjs arg1 arg2`
|
|
32
|
+
- **Do not:** `node -e "require('fs').writeFileSync(...)"` or any non-trivial inline JS.
|
|
33
|
+
|
|
34
|
+
### For file edits or transforms
|
|
35
|
+
|
|
36
|
+
1. **Prefer IDE/agent file tools** (StrReplace, Write, etc.) — they avoid the shell.
|
|
37
|
+
2. **Otherwise:** write a **temporary `.js` or `.mjs` file**, run it, then remove it. Use a **heredoc with quoted delimiter** so the shell does not interpret `$`, `` ` ``, or `!`:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
cat > /tmp/_transform.js << 'SCRIPT'
|
|
41
|
+
const fs = require('fs');
|
|
42
|
+
const data = JSON.parse(fs.readFileSync('package.json', 'utf8'));
|
|
43
|
+
data.name = 'my-app';
|
|
44
|
+
fs.writeFileSync('package.json', JSON.stringify(data, null, 2) + '\n');
|
|
45
|
+
SCRIPT
|
|
46
|
+
node /tmp/_transform.js && rm /tmp/_transform.js
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
3. **Simple replacements:** `sed -i '' 's/old/new/g' path/to/file` (macOS: `-i ''`).
|
|
50
|
+
4. **JSON:** `jq '.name = "my-app"' package.json > tmp.json && mv tmp.json package.json`.
|
|
51
|
+
|
|
52
|
+
### Quoting and shell safety
|
|
53
|
+
|
|
54
|
+
- Use **single quotes** around the outer shell string when the inner content has `$`, `` ` ``, or `!`.
|
|
55
|
+
- For heredocs that must be literal, use **`<< 'END'`** (quoted delimiter) so the body is not expanded.
|
|
56
|
+
- **Do not** generate commands that rely on Bash-only features (e.g. `[[ ]]` is fine in Zsh; avoid `source` vs `.` if you need POSIX).
|
|
57
|
+
|
|
58
|
+
### Paths and working directory
|
|
59
|
+
|
|
60
|
+
- **State where to run from** when it matters, e.g. "From project root" or "From `force-app/main/default/webapplications/<appName>`".
|
|
61
|
+
- Prefer **explicit paths** or `cd <dir> && ...` so the command is copy-paste safe.
|
|
62
|
+
- This project: setup is `node scripts/setup-cli.mjs --target-org <alias>` from **project root**. Web app commands (`npm run dev`, `npm run build`, `npm run lint`) run from the **web app directory** (e.g. `force-app/main/default/webapplications/<appName>` or `**/webapplications/<appName>`).
|
|
63
|
+
|
|
64
|
+
### npm / npx
|
|
65
|
+
|
|
66
|
+
- Use **exact package names** (e.g. `npx @salesforce/webapps-features-experimental list`).
|
|
67
|
+
- For app-specific scripts, **cd to the web app directory first**, then run `npm run <script>` or `npm install ...`.
|
|
68
|
+
- Chain steps with `&&`; one logical command per line is clearer than one giant line.
|
|
69
|
+
|
|
70
|
+
### Summary checklist when generating a command
|
|
71
|
+
|
|
72
|
+
- [ ] No `node -e` / `node -p` / `node --eval` with complex or multi-line code.
|
|
73
|
+
- [ ] If Node logic is needed, use `node path/to/script.mjs` or a temp script with heredoc `<< 'SCRIPT'`.
|
|
74
|
+
- [ ] No unescaped `!`, `` ` ``, or `$` in double-quoted strings in Zsh.
|
|
75
|
+
- [ ] Working directory and required args (e.g. `--target-org`) are clear.
|
|
76
|
+
- [ ] Prefer file-editing tools over shell one-liners when editing project files.
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## 3. Violation Handling
|
|
81
|
+
|
|
82
|
+
- If a generated command used a complex `node -e` one-liner, **revert and redo** using a script file, `sed`/`jq`, or IDE file tools.
|
|
83
|
+
- If the user sees `event not found`, `unexpected token`, or garbled output, **check for**:
|
|
84
|
+
- `node -e` with special characters,
|
|
85
|
+
- Double-quoted strings containing `!` or backticks,
|
|
86
|
+
- Wrong working directory or missing args.
|
|
87
|
+
|
|
88
|
+
**Cross-reference:** **webapp.md** (MUST FOLLOW #1) summarizes the no–`node -e` rule; this file is the full reference for CLI command generation and alternatives.
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Code quality and build validation standards
|
|
3
|
+
paths:
|
|
4
|
+
- "**/webapplications/**/*"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Code Quality & Build Validation
|
|
8
|
+
|
|
9
|
+
Enforces ESLint, TypeScript, and build validation for consistent, maintainable code.
|
|
10
|
+
|
|
11
|
+
## MANDATORY Quality Gates
|
|
12
|
+
|
|
13
|
+
**Before completing any coding session** (from the web app directory `force-app/main/default/webapplications/<appName>/`):
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm run lint # MUST result in 0 errors
|
|
17
|
+
npm run build # MUST succeed (includes TypeScript check)
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Requirements
|
|
21
|
+
|
|
22
|
+
**Must Pass:**
|
|
23
|
+
- `npm run build` completes successfully
|
|
24
|
+
- No TypeScript compilation errors
|
|
25
|
+
- No critical ESLint errors (0 errors)
|
|
26
|
+
|
|
27
|
+
**Can Be Warnings:**
|
|
28
|
+
- ESLint warnings (fix when convenient)
|
|
29
|
+
- Minor TypeScript warnings
|
|
30
|
+
|
|
31
|
+
## Key Commands
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
npm run dev # Start development server (vite)
|
|
35
|
+
npm run lint # Run ESLint
|
|
36
|
+
npm run build # TypeScript + Vite build; check deployment readiness
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Error Priority
|
|
40
|
+
|
|
41
|
+
**Fix Immediately:**
|
|
42
|
+
- TypeScript compilation errors
|
|
43
|
+
- Import/export errors
|
|
44
|
+
- Syntax errors
|
|
45
|
+
|
|
46
|
+
**Fix When Convenient:**
|
|
47
|
+
- ESLint warnings
|
|
48
|
+
- Unused variables
|
|
49
|
+
|
|
50
|
+
## Import Order (MANDATORY)
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
// 1. React ecosystem
|
|
54
|
+
import { useState, useEffect } from 'react';
|
|
55
|
+
|
|
56
|
+
// 2. External libraries (alphabetical)
|
|
57
|
+
import clsx from 'clsx';
|
|
58
|
+
|
|
59
|
+
// 3. UI components (alphabetical)
|
|
60
|
+
import { Button } from '@/components/ui/button';
|
|
61
|
+
import { Card } from '@/components/ui/card';
|
|
62
|
+
|
|
63
|
+
// 4. Internal utilities (alphabetical)
|
|
64
|
+
import { useAuth } from '../hooks/useAuth';
|
|
65
|
+
import { formatDate } from '../utils/dateUtils';
|
|
66
|
+
|
|
67
|
+
// 5. Relative imports
|
|
68
|
+
import { ComponentA } from './ComponentA';
|
|
69
|
+
|
|
70
|
+
// 6. Type imports (separate, at end)
|
|
71
|
+
import type { User, ApiResponse } from '../types';
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Naming Conventions
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
// PascalCase: Components, classes
|
|
78
|
+
const UserProfile = () => {};
|
|
79
|
+
|
|
80
|
+
// camelCase: Variables, functions, properties
|
|
81
|
+
const userName = 'john';
|
|
82
|
+
const fetchUserData = async () => {};
|
|
83
|
+
|
|
84
|
+
// SCREAMING_SNAKE_CASE: Constants
|
|
85
|
+
const API_BASE_URL = 'https://api.example.com';
|
|
86
|
+
|
|
87
|
+
// kebab-case: Files
|
|
88
|
+
// user-profile.tsx, api-client.ts
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## React Component Structure
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
interface ComponentProps {
|
|
95
|
+
// Props interface first
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const Component: React.FC<ComponentProps> = ({ prop1, prop2 }) => {
|
|
99
|
+
// 1. Hooks
|
|
100
|
+
// 2. Computed values
|
|
101
|
+
// 3. Event handlers
|
|
102
|
+
// 4. JSX return
|
|
103
|
+
|
|
104
|
+
return <div />;
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
export default Component;
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Anti-Patterns (FORBIDDEN)
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
// NEVER disable ESLint without justification
|
|
114
|
+
// eslint-disable-next-line
|
|
115
|
+
|
|
116
|
+
// NEVER mutate state directly
|
|
117
|
+
state.items.push(newItem); // Wrong
|
|
118
|
+
setItems(prev => [...prev, newItem]); // Correct
|
|
119
|
+
|
|
120
|
+
// NEVER use magic numbers/strings
|
|
121
|
+
setTimeout(() => {}, 5000); // Wrong
|
|
122
|
+
const DEBOUNCE_DELAY = 5000; // Correct
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## Troubleshooting
|
|
126
|
+
|
|
127
|
+
**Import Errors:**
|
|
128
|
+
```bash
|
|
129
|
+
npm install # Check missing dependencies
|
|
130
|
+
# Verify: file exists, case sensitivity, export/import match
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
**Build Failures:**
|
|
134
|
+
1. Run `npm run lint` to identify issues
|
|
135
|
+
2. Fix TypeScript/ESLint errors
|
|
136
|
+
3. Retry `npm run build`
|