@salesforce/templates 66.7.6 → 66.7.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/lib/templates/project/reactexternalapp/AGENT.md +84 -25
  2. package/lib/templates/project/reactexternalapp/CHANGELOG.md +86 -0
  3. package/lib/templates/project/reactexternalapp/README.md +60 -5
  4. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/package.json +3 -3
  5. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/utils/filterUtils.ts +10 -1
  6. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/authentication/menu/AuthMenu.tsx +93 -0
  7. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/appLayout.tsx +81 -76
  8. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/components/ui/avatar.tsx +109 -0
  9. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/components/ui/dropdown-menu.tsx +257 -0
  10. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/components/ui/index.ts +25 -0
  11. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/routes.tsx +2 -2
  12. package/lib/templates/project/reactexternalapp/package.json +2 -2
  13. package/lib/templates/project/reactexternalapp/scripts/gitignore-templates.json +4 -0
  14. package/lib/templates/project/reactexternalapp/scripts/{setup-cli.mjs → org-setup.mjs} +45 -6
  15. package/lib/templates/project/reactexternalapp/scripts/sf-project-setup.mjs +40 -1
  16. package/lib/templates/project/reactinternalapp/AGENT.md +84 -25
  17. package/lib/templates/project/reactinternalapp/CHANGELOG.md +86 -0
  18. package/lib/templates/project/reactinternalapp/README.md +61 -6
  19. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/package.json +3 -3
  20. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/utils/filterUtils.ts +10 -1
  21. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/components/AgentforceConversationClient.tsx +36 -3
  22. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/components/ui/avatar.tsx +109 -0
  23. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/components/ui/dropdown-menu.tsx +257 -0
  24. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/components/ui/index.ts +25 -0
  25. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/types/conversation.ts +2 -0
  26. package/lib/templates/project/reactinternalapp/package.json +2 -2
  27. package/lib/templates/project/reactinternalapp/scripts/gitignore-templates.json +4 -0
  28. package/lib/templates/project/reactinternalapp/scripts/{setup-cli.mjs → org-setup.mjs} +45 -6
  29. package/lib/templates/project/reactinternalapp/scripts/sf-project-setup.mjs +40 -1
  30. package/lib/templates/uiBundles/reactbasic/package.json +3 -3
  31. package/lib/templates/uiBundles/reactbasic/src/components/ui/avatar.tsx +109 -0
  32. package/lib/templates/uiBundles/reactbasic/src/components/ui/dropdown-menu.tsx +257 -0
  33. package/lib/templates/uiBundles/reactbasic/src/components/ui/index.ts +25 -0
  34. package/package.json +6 -6
@@ -0,0 +1,257 @@
1
+ 'use client';
2
+
3
+ import * as React from 'react';
4
+ import { CheckIcon, ChevronRightIcon, CircleIcon } from 'lucide-react';
5
+ import { DropdownMenu as DropdownMenuPrimitive } from 'radix-ui';
6
+
7
+ import { cn } from '@/lib/utils';
8
+
9
+ function DropdownMenu({
10
+ ...props
11
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Root>) {
12
+ return <DropdownMenuPrimitive.Root data-slot="dropdown-menu" {...props} />;
13
+ }
14
+
15
+ function DropdownMenuPortal({
16
+ ...props
17
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Portal>) {
18
+ return (
19
+ <DropdownMenuPrimitive.Portal data-slot="dropdown-menu-portal" {...props} />
20
+ );
21
+ }
22
+
23
+ function DropdownMenuTrigger({
24
+ ...props
25
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Trigger>) {
26
+ return (
27
+ <DropdownMenuPrimitive.Trigger
28
+ data-slot="dropdown-menu-trigger"
29
+ {...props}
30
+ />
31
+ );
32
+ }
33
+
34
+ function DropdownMenuContent({
35
+ className,
36
+ sideOffset = 4,
37
+ ...props
38
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Content>) {
39
+ return (
40
+ <DropdownMenuPrimitive.Portal>
41
+ <DropdownMenuPrimitive.Content
42
+ data-slot="dropdown-menu-content"
43
+ sideOffset={sideOffset}
44
+ className={cn(
45
+ 'z-50 max-h-(--radix-dropdown-menu-content-available-height) min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95',
46
+ className
47
+ )}
48
+ {...props}
49
+ />
50
+ </DropdownMenuPrimitive.Portal>
51
+ );
52
+ }
53
+
54
+ function DropdownMenuGroup({
55
+ ...props
56
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Group>) {
57
+ return (
58
+ <DropdownMenuPrimitive.Group data-slot="dropdown-menu-group" {...props} />
59
+ );
60
+ }
61
+
62
+ function DropdownMenuItem({
63
+ className,
64
+ inset,
65
+ variant = 'default',
66
+ ...props
67
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Item> & {
68
+ inset?: boolean;
69
+ variant?: 'default' | 'destructive';
70
+ }) {
71
+ return (
72
+ <DropdownMenuPrimitive.Item
73
+ data-slot="dropdown-menu-item"
74
+ data-inset={inset}
75
+ data-variant={variant}
76
+ className={cn(
77
+ "relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground data-[variant=destructive]:*:[svg]:text-destructive!",
78
+ className
79
+ )}
80
+ {...props}
81
+ />
82
+ );
83
+ }
84
+
85
+ function DropdownMenuCheckboxItem({
86
+ className,
87
+ children,
88
+ checked,
89
+ ...props
90
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.CheckboxItem>) {
91
+ return (
92
+ <DropdownMenuPrimitive.CheckboxItem
93
+ data-slot="dropdown-menu-checkbox-item"
94
+ className={cn(
95
+ "relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
96
+ className
97
+ )}
98
+ checked={checked}
99
+ {...props}
100
+ >
101
+ <span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
102
+ <DropdownMenuPrimitive.ItemIndicator>
103
+ <CheckIcon className="size-4" />
104
+ </DropdownMenuPrimitive.ItemIndicator>
105
+ </span>
106
+ {children}
107
+ </DropdownMenuPrimitive.CheckboxItem>
108
+ );
109
+ }
110
+
111
+ function DropdownMenuRadioGroup({
112
+ ...props
113
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.RadioGroup>) {
114
+ return (
115
+ <DropdownMenuPrimitive.RadioGroup
116
+ data-slot="dropdown-menu-radio-group"
117
+ {...props}
118
+ />
119
+ );
120
+ }
121
+
122
+ function DropdownMenuRadioItem({
123
+ className,
124
+ children,
125
+ ...props
126
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.RadioItem>) {
127
+ return (
128
+ <DropdownMenuPrimitive.RadioItem
129
+ data-slot="dropdown-menu-radio-item"
130
+ className={cn(
131
+ "relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
132
+ className
133
+ )}
134
+ {...props}
135
+ >
136
+ <span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
137
+ <DropdownMenuPrimitive.ItemIndicator>
138
+ <CircleIcon className="size-2 fill-current" />
139
+ </DropdownMenuPrimitive.ItemIndicator>
140
+ </span>
141
+ {children}
142
+ </DropdownMenuPrimitive.RadioItem>
143
+ );
144
+ }
145
+
146
+ function DropdownMenuLabel({
147
+ className,
148
+ inset,
149
+ ...props
150
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Label> & {
151
+ inset?: boolean;
152
+ }) {
153
+ return (
154
+ <DropdownMenuPrimitive.Label
155
+ data-slot="dropdown-menu-label"
156
+ data-inset={inset}
157
+ className={cn(
158
+ 'px-2 py-1.5 text-sm font-medium data-[inset]:pl-8',
159
+ className
160
+ )}
161
+ {...props}
162
+ />
163
+ );
164
+ }
165
+
166
+ function DropdownMenuSeparator({
167
+ className,
168
+ ...props
169
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Separator>) {
170
+ return (
171
+ <DropdownMenuPrimitive.Separator
172
+ data-slot="dropdown-menu-separator"
173
+ className={cn('-mx-1 my-1 h-px bg-border', className)}
174
+ {...props}
175
+ />
176
+ );
177
+ }
178
+
179
+ function DropdownMenuShortcut({
180
+ className,
181
+ ...props
182
+ }: React.ComponentProps<'span'>) {
183
+ return (
184
+ <span
185
+ data-slot="dropdown-menu-shortcut"
186
+ className={cn(
187
+ 'ml-auto text-xs tracking-widest text-muted-foreground',
188
+ className
189
+ )}
190
+ {...props}
191
+ />
192
+ );
193
+ }
194
+
195
+ function DropdownMenuSub({
196
+ ...props
197
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Sub>) {
198
+ return <DropdownMenuPrimitive.Sub data-slot="dropdown-menu-sub" {...props} />;
199
+ }
200
+
201
+ function DropdownMenuSubTrigger({
202
+ className,
203
+ inset,
204
+ children,
205
+ ...props
206
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.SubTrigger> & {
207
+ inset?: boolean;
208
+ }) {
209
+ return (
210
+ <DropdownMenuPrimitive.SubTrigger
211
+ data-slot="dropdown-menu-sub-trigger"
212
+ data-inset={inset}
213
+ className={cn(
214
+ "flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[inset]:pl-8 data-[state=open]:bg-accent data-[state=open]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground",
215
+ className
216
+ )}
217
+ {...props}
218
+ >
219
+ {children}
220
+ <ChevronRightIcon className="ml-auto size-4" />
221
+ </DropdownMenuPrimitive.SubTrigger>
222
+ );
223
+ }
224
+
225
+ function DropdownMenuSubContent({
226
+ className,
227
+ ...props
228
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.SubContent>) {
229
+ return (
230
+ <DropdownMenuPrimitive.SubContent
231
+ data-slot="dropdown-menu-sub-content"
232
+ className={cn(
233
+ 'z-50 min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95',
234
+ className
235
+ )}
236
+ {...props}
237
+ />
238
+ );
239
+ }
240
+
241
+ export {
242
+ DropdownMenu,
243
+ DropdownMenuPortal,
244
+ DropdownMenuTrigger,
245
+ DropdownMenuContent,
246
+ DropdownMenuGroup,
247
+ DropdownMenuLabel,
248
+ DropdownMenuItem,
249
+ DropdownMenuCheckboxItem,
250
+ DropdownMenuRadioGroup,
251
+ DropdownMenuRadioItem,
252
+ DropdownMenuSeparator,
253
+ DropdownMenuShortcut,
254
+ DropdownMenuSub,
255
+ DropdownMenuSubTrigger,
256
+ DropdownMenuSubContent,
257
+ };
@@ -13,6 +13,14 @@
13
13
  */
14
14
 
15
15
  export { Alert, AlertTitle, AlertDescription, AlertAction } from './alert';
16
+ export {
17
+ Avatar,
18
+ AvatarImage,
19
+ AvatarFallback,
20
+ AvatarBadge,
21
+ AvatarGroup,
22
+ AvatarGroupCount,
23
+ } from './avatar';
16
24
  export { Button, buttonVariants } from './button';
17
25
  export {
18
26
  Card,
@@ -35,6 +43,23 @@ export {
35
43
  DialogTitle,
36
44
  DialogTrigger,
37
45
  } from './dialog';
46
+ export {
47
+ DropdownMenu,
48
+ DropdownMenuPortal,
49
+ DropdownMenuTrigger,
50
+ DropdownMenuContent,
51
+ DropdownMenuGroup,
52
+ DropdownMenuLabel,
53
+ DropdownMenuItem,
54
+ DropdownMenuCheckboxItem,
55
+ DropdownMenuRadioGroup,
56
+ DropdownMenuRadioItem,
57
+ DropdownMenuSeparator,
58
+ DropdownMenuShortcut,
59
+ DropdownMenuSub,
60
+ DropdownMenuSubTrigger,
61
+ DropdownMenuSubContent,
62
+ } from './dropdown-menu';
38
63
  export {
39
64
  Field,
40
65
  FieldDescription,
@@ -14,6 +14,8 @@ export type StyleTokens = Record<string, string>;
14
14
  export interface AgentforceConversationClientProps {
15
15
  /** Required in practice: id of the agent to load. */
16
16
  agentId: string;
17
+ /** Display name for the agent shown in the chat header. */
18
+ agentLabel?: string;
17
19
  /** If true, renders inline. If omitted/false, renders floating. */
18
20
  inline?: boolean;
19
21
  /** Show/hide chat header. Defaults to true for floating; can only be set for inline mode. */
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@salesforce/ui-bundle-template-base-sfdx-project",
3
- "version": "1.117.5",
3
+ "version": "1.119.4",
4
4
  "description": "Base SFDX project template",
5
5
  "license": "SEE LICENSE IN LICENSE.txt",
6
6
  "publishConfig": {
@@ -20,7 +20,7 @@
20
20
  "prettier": "prettier --write \"**/*.{cls,cmp,component,css,html,js,json,md,page,trigger,xml,yaml,yml}\"",
21
21
  "prettier:verify": "prettier --check \"**/*.{cls,cmp,component,css,html,js,json,md,page,trigger,xml,yaml,yml}\"",
22
22
  "precommit": "lint-staged",
23
- "setup": "node scripts/setup-cli.mjs"
23
+ "setup": "node scripts/org-setup.mjs"
24
24
  },
25
25
  "devDependencies": {
26
26
  "@lwc/eslint-plugin-lwc": "^3.3.0",
@@ -0,0 +1,4 @@
1
+ {
2
+ "sfdx": "# This file is used for Git repositories to specify intentionally untracked files that Git should ignore. \n# If you are not using git, you can delete this file. For more information see: https://git-scm.com/docs/gitignore\n# For useful gitignore templates see: https://github.com/github/gitignore\n\n# Salesforce cache\n.sf/\n.sfdx/\n.localdevserver/\ndeploy-options.json\n\n# LWC VSCode autocomplete\n**/lwc/jsconfig.json\n\n# LWC Jest coverage reports\ncoverage/\n\n# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n\n# Dependency directories\nnode_modules/\n\n# Eslint cache\n.eslintcache\n\n# MacOS system files\n.DS_Store\n\n# Windows system files\nThumbs.db\nehthumbs.db\n[Dd]esktop.ini\n$RECYCLE.BIN/\n\n# Local environment variables\n.env\n\n# Python Salesforce Functions\n**/__pycache__/\n**/.venv/\n**/venv/\n",
3
+ "webapp": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndist-ssr\nbuild\npackage-lock.json\n*.local\n\n# Playwright\n/test-results/\n/playwright-report/\n/blob-report/\n/playwright/.cache/\n\n# Editor directories and files\n.vscode/*\n!.vscode/extensions.json\n.idea\n.DS_Store\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n\n# Un-exclude Cursor AI rules for this SFDX project\n!.a4drules/\n!.cursor/\n"
4
+ }
@@ -4,11 +4,11 @@
4
4
  * Use this script to make setup easier for each app generated from this template.
5
5
  *
6
6
  * Usage:
7
- * node scripts/setup-cli.mjs --target-org <alias> # interactive step picker (all selected)
8
- * node scripts/setup-cli.mjs --target-org <alias> --yes # skip picker, run all steps
9
- * node scripts/setup-cli.mjs --target-org afv5 --skip-login
10
- * node scripts/setup-cli.mjs --target-org afv5 --skip-data --skip-ui-bundle-build
11
- * node scripts/setup-cli.mjs --target-org myorg --ui-bundle-name my-app
7
+ * node scripts/org-setup.mjs --target-org <alias> # interactive step picker (all selected)
8
+ * node scripts/org-setup.mjs --target-org <alias> --yes # skip picker, run all steps
9
+ * node scripts/org-setup.mjs --target-org afv5 --skip-login
10
+ * node scripts/org-setup.mjs --target-org afv5 --skip-data --skip-ui-bundle-build
11
+ * node scripts/org-setup.mjs --target-org myorg --ui-bundle-name my-app
12
12
  *
13
13
  * Steps (in order):
14
14
  * 1. login — sf org login web only if org not already connected (skip with --skip-login)
@@ -28,6 +28,32 @@ import { readdirSync, existsSync, readFileSync, writeFileSync, unlinkSync } from
28
28
  const __dirname = dirname(fileURLToPath(import.meta.url));
29
29
  const ROOT = resolve(__dirname, '..');
30
30
 
31
+ /**
32
+ * npm strips .gitignore from published packages — generate them on first run.
33
+ * Templates are stored in scripts/gitignore-templates.json (generated at build
34
+ * time from the actual .gitignore files) so the content lives in one place.
35
+ * The JSON may not exist in git-cloned distributions where .gitignore is
36
+ * already present, so loading is best-effort.
37
+ */
38
+ function loadGitignoreTemplates() {
39
+ const templatesPath = resolve(__dirname, 'gitignore-templates.json');
40
+ if (!existsSync(templatesPath)) return null;
41
+ try {
42
+ return JSON.parse(readFileSync(templatesPath, 'utf8'));
43
+ } catch {
44
+ return null;
45
+ }
46
+ }
47
+
48
+ function ensureGitignore(dir, content) {
49
+ if (!content) return;
50
+ const gitignorePath = resolve(dir, '.gitignore');
51
+ if (!existsSync(gitignorePath)) {
52
+ writeFileSync(gitignorePath, content, 'utf8');
53
+ console.log(`Created .gitignore in ${dir}`);
54
+ }
55
+ }
56
+
31
57
  function resolveSfdxSource() {
32
58
  const sfdxPath = resolve(ROOT, 'sfdx-project.json');
33
59
  if (!existsSync(sfdxPath)) {
@@ -84,7 +110,7 @@ function parseArgs() {
84
110
  Setup CLI — one-command setup for apps in this project
85
111
 
86
112
  Usage:
87
- node scripts/setup-cli.mjs --target-org <alias> [options]
113
+ node scripts/org-setup.mjs --target-org <alias> [options]
88
114
 
89
115
  Required:
90
116
  --target-org <alias> Target Salesforce org alias (e.g. myorg)
@@ -292,6 +318,19 @@ function run(name, cmd, args, opts = {}) {
292
318
  }
293
319
 
294
320
  async function main() {
321
+ // Ensure .gitignore files exist (npm strips them from published packages).
322
+ const gitignoreTemplates = loadGitignoreTemplates();
323
+ if (gitignoreTemplates) {
324
+ ensureGitignore(ROOT, gitignoreTemplates.sfdx);
325
+ if (existsSync(UIBUNDLES_DIR)) {
326
+ for (const entry of readdirSync(UIBUNDLES_DIR, { withFileTypes: true })) {
327
+ if (entry.isDirectory() && !entry.name.startsWith('.')) {
328
+ ensureGitignore(resolve(UIBUNDLES_DIR, entry.name), gitignoreTemplates.webapp);
329
+ }
330
+ }
331
+ }
332
+ }
333
+
295
334
  const {
296
335
  targetOrg,
297
336
  uiBundleName,
@@ -10,11 +10,36 @@
10
10
  import { spawnSync } from 'node:child_process';
11
11
  import { resolve, dirname } from 'node:path';
12
12
  import { fileURLToPath } from 'node:url';
13
- import { readdirSync, existsSync, readFileSync } from 'node:fs';
13
+ import { readdirSync, existsSync, readFileSync, writeFileSync } from 'node:fs';
14
14
 
15
15
  const __dirname = dirname(fileURLToPath(import.meta.url));
16
16
  const ROOT = resolve(__dirname, '..');
17
17
 
18
+ /**
19
+ * npm strips .gitignore from published packages — generate them on first run.
20
+ * Templates are stored in scripts/gitignore-templates.json (generated at build
21
+ * time from the actual .gitignore files). The JSON may not exist in git-cloned
22
+ * distributions where .gitignore is already present, so loading is best-effort.
23
+ */
24
+ function loadGitignoreTemplates() {
25
+ const templatesPath = resolve(__dirname, 'gitignore-templates.json');
26
+ if (!existsSync(templatesPath)) return null;
27
+ try {
28
+ return JSON.parse(readFileSync(templatesPath, 'utf8'));
29
+ } catch {
30
+ return null;
31
+ }
32
+ }
33
+
34
+ function ensureGitignore(dir, content) {
35
+ if (!content) return;
36
+ const gitignorePath = resolve(dir, '.gitignore');
37
+ if (!existsSync(gitignorePath)) {
38
+ writeFileSync(gitignorePath, content, 'utf8');
39
+ console.log(`Created .gitignore in ${dir}`);
40
+ }
41
+ }
42
+
18
43
  function resolveUIBundlesDir() {
19
44
  const sfdxPath = resolve(ROOT, 'sfdx-project.json');
20
45
  if (!existsSync(sfdxPath)) {
@@ -56,6 +81,20 @@ function run(label, cmd, args, opts) {
56
81
  }
57
82
  }
58
83
 
84
+ // Ensure .gitignore files exist (npm strips them from published packages).
85
+ const gitignoreTemplates = loadGitignoreTemplates();
86
+ if (gitignoreTemplates) {
87
+ ensureGitignore(ROOT, gitignoreTemplates.sfdx);
88
+ const bundlesDir = resolveUIBundlesDir();
89
+ if (existsSync(bundlesDir)) {
90
+ for (const entry of readdirSync(bundlesDir, { withFileTypes: true })) {
91
+ if (entry.isDirectory() && !entry.name.startsWith('.')) {
92
+ ensureGitignore(resolve(bundlesDir, entry.name), gitignoreTemplates.webapp);
93
+ }
94
+ }
95
+ }
96
+ }
97
+
59
98
  const uiBundleDir = discoverUIBundleDir();
60
99
  console.log('SFDX project root:', ROOT);
61
100
  console.log('UI bundle directory:', uiBundleDir);
@@ -15,8 +15,8 @@
15
15
  "graphql:schema": "node scripts/get-graphql-schema.mjs"
16
16
  },
17
17
  "dependencies": {
18
- "@salesforce/sdk-data": "^1.117.5",
19
- "@salesforce/ui-bundle": "^1.117.5",
18
+ "@salesforce/sdk-data": "^1.119.4",
19
+ "@salesforce/ui-bundle": "^1.119.4",
20
20
  "@tailwindcss/vite": "^4.1.17",
21
21
  "class-variance-authority": "^0.7.1",
22
22
  "clsx": "^2.1.1",
@@ -41,7 +41,7 @@
41
41
  "@graphql-eslint/eslint-plugin": "^4.1.0",
42
42
  "@graphql-tools/utils": "^11.0.0",
43
43
  "@playwright/test": "^1.49.0",
44
- "@salesforce/vite-plugin-ui-bundle": "^1.117.5",
44
+ "@salesforce/vite-plugin-ui-bundle": "^1.119.4",
45
45
  "@testing-library/jest-dom": "^6.6.3",
46
46
  "@testing-library/react": "^16.1.0",
47
47
  "@testing-library/user-event": "^14.5.2",
@@ -0,0 +1,109 @@
1
+ 'use client';
2
+
3
+ import * as React from 'react';
4
+ import { Avatar as AvatarPrimitive } from 'radix-ui';
5
+
6
+ import { cn } from '@/lib/utils';
7
+
8
+ function Avatar({
9
+ className,
10
+ size = 'default',
11
+ ...props
12
+ }: React.ComponentProps<typeof AvatarPrimitive.Root> & {
13
+ size?: 'default' | 'sm' | 'lg';
14
+ }) {
15
+ return (
16
+ <AvatarPrimitive.Root
17
+ data-slot="avatar"
18
+ data-size={size}
19
+ className={cn(
20
+ 'group/avatar relative flex size-8 shrink-0 overflow-hidden rounded-full select-none data-[size=lg]:size-10 data-[size=sm]:size-6',
21
+ className
22
+ )}
23
+ {...props}
24
+ />
25
+ );
26
+ }
27
+
28
+ function AvatarImage({
29
+ className,
30
+ ...props
31
+ }: React.ComponentProps<typeof AvatarPrimitive.Image>) {
32
+ return (
33
+ <AvatarPrimitive.Image
34
+ data-slot="avatar-image"
35
+ className={cn('aspect-square size-full', className)}
36
+ {...props}
37
+ />
38
+ );
39
+ }
40
+
41
+ function AvatarFallback({
42
+ className,
43
+ ...props
44
+ }: React.ComponentProps<typeof AvatarPrimitive.Fallback>) {
45
+ return (
46
+ <AvatarPrimitive.Fallback
47
+ data-slot="avatar-fallback"
48
+ className={cn(
49
+ 'flex size-full items-center justify-center rounded-full bg-muted text-sm text-muted-foreground group-data-[size=sm]/avatar:text-xs',
50
+ className
51
+ )}
52
+ {...props}
53
+ />
54
+ );
55
+ }
56
+
57
+ function AvatarBadge({ className, ...props }: React.ComponentProps<'span'>) {
58
+ return (
59
+ <span
60
+ data-slot="avatar-badge"
61
+ className={cn(
62
+ 'absolute right-0 bottom-0 z-10 inline-flex items-center justify-center rounded-full bg-primary text-primary-foreground ring-2 ring-background select-none',
63
+ 'group-data-[size=sm]/avatar:size-2 group-data-[size=sm]/avatar:[&>svg]:hidden',
64
+ 'group-data-[size=default]/avatar:size-2.5 group-data-[size=default]/avatar:[&>svg]:size-2',
65
+ 'group-data-[size=lg]/avatar:size-3 group-data-[size=lg]/avatar:[&>svg]:size-2',
66
+ className
67
+ )}
68
+ {...props}
69
+ />
70
+ );
71
+ }
72
+
73
+ function AvatarGroup({ className, ...props }: React.ComponentProps<'div'>) {
74
+ return (
75
+ <div
76
+ data-slot="avatar-group"
77
+ className={cn(
78
+ 'group/avatar-group flex -space-x-2 *:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:ring-background',
79
+ className
80
+ )}
81
+ {...props}
82
+ />
83
+ );
84
+ }
85
+
86
+ function AvatarGroupCount({
87
+ className,
88
+ ...props
89
+ }: React.ComponentProps<'div'>) {
90
+ return (
91
+ <div
92
+ data-slot="avatar-group-count"
93
+ className={cn(
94
+ 'relative flex size-8 shrink-0 items-center justify-center rounded-full bg-muted text-sm text-muted-foreground ring-2 ring-background group-has-data-[size=lg]/avatar-group:size-10 group-has-data-[size=sm]/avatar-group:size-6 [&>svg]:size-4 group-has-data-[size=lg]/avatar-group:[&>svg]:size-5 group-has-data-[size=sm]/avatar-group:[&>svg]:size-3',
95
+ className
96
+ )}
97
+ {...props}
98
+ />
99
+ );
100
+ }
101
+
102
+ export {
103
+ Avatar,
104
+ AvatarImage,
105
+ AvatarFallback,
106
+ AvatarBadge,
107
+ AvatarGroup,
108
+ AvatarGroupCount,
109
+ };