@bardioc/create-bardioc-app 0.6.1 → 0.7.1

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 (54) hide show
  1. package/README.md +5 -1
  2. package/package.json +2 -2
  3. package/templates/_base/.changeset/README.md +4 -6
  4. package/templates/_base/.claude/commands/changeset-app.md +11 -12
  5. package/templates/_base/.claude/commands/refresh-bundle.md +3 -3
  6. package/templates/_base/AGENTS.md +128 -0
  7. package/templates/_base/CLAUDE.md +1 -0
  8. package/templates/_base/README.md +17 -20
  9. package/templates/_base/scripts/audit.mjs +50 -0
  10. package/templates/_base/scripts/compliance-summary.mjs +42 -0
  11. package/templates/_base/scripts/compliance.mjs +64 -0
  12. package/templates/_base/scripts/stamp-manifest.mjs +10 -4
  13. package/templates/_opt-link/scripts/link-source.mjs +1 -1
  14. package/templates/_opt-pipeline/bitbucket-pipelines.yml +12 -56
  15. package/templates/angular/_gitignore +6 -0
  16. package/templates/angular/package.json +8 -2
  17. package/templates/nextjs/_gitignore +6 -0
  18. package/templates/nextjs/package.json +8 -2
  19. package/templates/preact/_gitignore +6 -0
  20. package/templates/preact/package.json +8 -2
  21. package/templates/solid/_gitignore +6 -0
  22. package/templates/solid/package.json +8 -2
  23. package/templates/svelte/_gitignore +6 -0
  24. package/templates/svelte/package.json +8 -2
  25. package/templates/vite/CLAUDE.md +149 -135
  26. package/templates/vite/_gitignore +6 -0
  27. package/templates/vite/package.json +10 -4
  28. package/templates/vite/src/App.tsx +6 -3
  29. package/templates/vite/src/auth/onboarding.tsx +19 -23
  30. package/templates/vite/src/climate/climate-data.ts +19 -0
  31. package/templates/vite/src/climate/conditions-chart.tsx +11 -6
  32. package/templates/vite/src/climate/location-card.tsx +2 -5
  33. package/templates/vite/src/climate/location-select.tsx +7 -2
  34. package/templates/vite/src/climate/metrics-grid.tsx +6 -1
  35. package/templates/vite/src/climate/overview-view.tsx +26 -7
  36. package/templates/vite/src/climate/stations-view.tsx +73 -9
  37. package/templates/vite/src/climate/types.ts +5 -5
  38. package/templates/vite/src/dashboard-provider.tsx +15 -5
  39. package/templates/vite/src/events/live-events-view.tsx +67 -37
  40. package/templates/vite/src/events/mock-events.ts +33 -0
  41. package/templates/vite/src/graph/explorer-view.tsx +81 -63
  42. package/templates/vite/src/i18n.tsx +6 -2
  43. package/templates/vite/src/index.css +0 -6
  44. package/templates/vite/src/locales/de.json +55 -51
  45. package/templates/vite/src/locales/en.json +44 -40
  46. package/templates/vite/src/preview-state.tsx +29 -0
  47. package/templates/vite/src/profile/profile-view.tsx +43 -25
  48. package/templates/vite/src/shell/about-dialog.tsx +6 -5
  49. package/templates/vite/src/shell/app-shell.tsx +46 -45
  50. package/templates/vite/src/shell/debug-menu.tsx +86 -0
  51. package/templates/vite/src/shell/header.tsx +6 -4
  52. package/templates/vite/src/shell/view-content.tsx +69 -0
  53. package/templates/vue/_gitignore +6 -0
  54. package/templates/vue/package.json +8 -2
@@ -1,5 +1,5 @@
1
- import { Avatar, Card, Empty } from '@bardioc/ui/components';
2
- import { UserX } from 'lucide-react';
1
+ import { Avatar, Card, Empty, SkeletonWrapper } from '@bardioc/ui/components';
2
+ import { UserRound, UserX } from 'lucide-react';
3
3
  import { useDashboard } from '../dashboard-provider';
4
4
  import { useTranslation } from '../i18n';
5
5
  import { operatorLabel } from './use-profile';
@@ -22,17 +22,31 @@ export function ProfileView() {
22
22
  const { profile } = useDashboard();
23
23
  const unknown = t('profile.unknown', { fallback: 'Unknown' });
24
24
 
25
- if (profile.loading) {
26
- return <p className="text-muted-foreground p-6 text-sm">…</p>;
25
+ if (!profile.loading && !profile.error && !profile.profile) {
26
+ return (
27
+ <Empty
28
+ icon={UserRound}
29
+ iconVariant="chip"
30
+ title={t('profile.emptyTitle', { fallback: 'No Profile Data' })}
31
+ description={t('profile.emptyDescription', {
32
+ fallback: 'The OS returned no profile for this session.',
33
+ })}
34
+ />
35
+ );
27
36
  }
28
37
 
29
- if (profile.error || !profile.profile) {
38
+ if (!profile.loading && profile.error) {
30
39
  return (
31
40
  <Empty
32
41
  icon={UserX}
33
42
  iconVariant="chip"
34
43
  title={t('profile.errorTitle', { fallback: 'Could Not Load Profile' })}
35
- description={profile.error ?? t('profile.errorHint', { fallback: '' })}
44
+ description={
45
+ profile.error ??
46
+ t('profile.errorHint', {
47
+ fallback: 'The transport returned an error. Authorize a session, then reload.',
48
+ })
49
+ }
36
50
  />
37
51
  );
38
52
  }
@@ -42,27 +56,31 @@ export function ProfileView() {
42
56
 
43
57
  return (
44
58
  <div className="flex flex-col gap-4">
45
- <p className="text-muted-foreground text-xs">{t('profile.description', { fallback: '' })}</p>
59
+ <p className="text-muted-foreground text-xs">
60
+ {t('profile.description', { fallback: 'Pulled live from the OS transport (os.profile).' })}
61
+ </p>
46
62
 
47
- <Card>
48
- <div className="flex items-center gap-4">
49
- <Avatar firstName={name} size="lg" />
50
- <div className="flex min-w-0 flex-1 flex-col gap-1">
51
- <Row
52
- label={t('profile.displayName', { fallback: 'Display Name' })}
53
- value={asString(data.display_name, unknown)}
54
- />
55
- <Row
56
- label={t('profile.firstName', { fallback: 'First Name' })}
57
- value={asString(data.first_name, unknown)}
58
- />
59
- <Row
60
- label={t('profile.lastName', { fallback: 'Last Name' })}
61
- value={asString(data.last_name, unknown)}
62
- />
63
+ <SkeletonWrapper loading={profile.loading}>
64
+ <Card>
65
+ <div className="flex items-center gap-4">
66
+ <Avatar firstName={name} size="lg" />
67
+ <div className="flex min-w-0 flex-1 flex-col gap-1">
68
+ <Row
69
+ label={t('profile.displayName', { fallback: 'Display Name' })}
70
+ value={asString(data?.display_name, unknown)}
71
+ />
72
+ <Row
73
+ label={t('profile.firstName', { fallback: 'First Name' })}
74
+ value={asString(data?.first_name, unknown)}
75
+ />
76
+ <Row
77
+ label={t('profile.lastName', { fallback: 'Last Name' })}
78
+ value={asString(data?.last_name, unknown)}
79
+ />
80
+ </div>
63
81
  </div>
64
- </div>
65
- </Card>
82
+ </Card>
83
+ </SkeletonWrapper>
66
84
  </div>
67
85
  );
68
86
  }
@@ -1,4 +1,4 @@
1
- import { BardiocLogoIcon, Dialog } from '@bardioc/ui/components';
1
+ import { Alert, BardiocLogoIcon, Dialog } from '@bardioc/ui/components';
2
2
  import { HashIcon, PackageIcon, TagIcon } from 'lucide-react';
3
3
  import { useEffect, useState, type ReactNode } from 'react';
4
4
  import { useTranslation } from '../i18n';
@@ -34,7 +34,7 @@ function AboutRow({
34
34
  value,
35
35
  }: Readonly<{ icon: ReactNode; label: string; value: string }>) {
36
36
  return (
37
- <div className="flex items-center justify-between gap-4 rounded-lg p-2">
37
+ <div className="flex items-center justify-between gap-4 p-2">
38
38
  <div className="text-foreground flex items-center gap-2">
39
39
  {icon}
40
40
  <span className="text-xs font-semibold">{label}</span>
@@ -91,9 +91,10 @@ export function AboutDialog({ open, onOpenChange }: Readonly<AboutDialogProps>)
91
91
  </div>
92
92
 
93
93
  {failed ? (
94
- <p className="text-destructive text-sm">
95
- {t('about.error', { fallback: 'Could not read app-manifest.json.' })}
96
- </p>
94
+ <Alert
95
+ variant="destructive"
96
+ description={t('about.error', { fallback: 'Could not read app-manifest.json.' })}
97
+ />
97
98
  ) : (
98
99
  <div className="w-full">
99
100
  <AboutRow
@@ -1,12 +1,14 @@
1
1
  import type { MenuRoot } from '@bardioc/app-sdk';
2
2
  import { useCommandMenu, type AppCommand } from '@bardioc/app-sdk/react';
3
3
  import {
4
- BardiocLogoIcon,
5
4
  Page,
6
5
  SidebarBasic,
7
6
  SidebarContainedProvider,
7
+ SidebarContainedTrigger,
8
+ SidebarSearch,
8
9
  Toaster,
9
10
  } from '@bardioc/ui/components';
11
+ import { useFuzzySearch } from '@bardioc/ui/hooks';
10
12
  import {
11
13
  Activity,
12
14
  LayoutDashboard,
@@ -16,16 +18,13 @@ import {
16
18
  type LucideIcon,
17
19
  } from 'lucide-react';
18
20
  import { useEffect, useMemo, useRef, useState } from 'react';
19
- import { OverviewView } from '../climate/overview-view';
20
- import { StationsView } from '../climate/stations-view';
21
21
  import type { ViewId } from '../climate/types';
22
- import { LiveEventsView } from '../events/live-events-view';
23
- import { ExplorerView } from '../graph/explorer-view';
24
22
  import { useI18n, useTranslation } from '../i18n';
25
- import { ProfileView } from '../profile/profile-view';
26
23
  import { useAppNotify } from '../use-app-notify';
27
24
  import { AboutDialog } from './about-dialog';
25
+ import { DebugMenu } from './debug-menu';
28
26
  import { Header } from './header';
27
+ import { ViewContent } from './view-content';
29
28
 
30
29
  // `icon` is the sidebar's React component; `iconName` is the matching lucide export name the
31
30
  // OS resolves for the top-menu entry (host calls resolveLucideIcon on the string). `titleKey` is
@@ -74,47 +73,39 @@ const NAV: Array<{
74
73
  },
75
74
  ];
76
75
 
77
- function ViewContent({ view }: Readonly<{ view: ViewId }>) {
78
- switch (view) {
79
- case 'overview':
80
- return <OverviewView />;
81
- case 'stations':
82
- return <StationsView />;
83
- case 'explorer':
84
- return <ExplorerView />;
85
- case 'events':
86
- return <LiveEventsView />;
87
- case 'profile':
88
- return <ProfileView />;
89
- default:
90
- return null;
91
- }
92
- }
93
-
94
76
  export function AppShell() {
95
77
  const { t } = useTranslation();
96
78
  const { locale } = useI18n();
97
79
  const notify = useAppNotify();
98
80
  const [view, setView] = useState<ViewId>('overview');
99
81
  const [aboutOpen, setAboutOpen] = useState(false);
82
+ const [navQuery, setNavQuery] = useState('');
100
83
 
101
- const firstLocale = useRef(true);
84
+ // Compared with the last locale, not skipped once: `t` changes identity when the catalog loads,
85
+ // which re-runs this effect without the language changing.
86
+ const shownLocale = useRef(locale);
102
87
  useEffect(() => {
103
- if (firstLocale.current) {
104
- firstLocale.current = false;
105
- return;
106
- }
88
+ if (shownLocale.current === locale) return;
89
+ shownLocale.current = locale;
107
90
  notify(
108
91
  t('notify.localeChanged', { fallback: 'Language switched to {locale}.', params: { locale } }),
109
92
  'info'
110
93
  );
111
94
  }, [locale, notify, t]);
112
95
 
113
- const items = NAV.map(entry => ({
114
- name: entry.id,
115
- label: t(entry.labelKey, { fallback: entry.id }),
116
- icon: entry.icon,
117
- }));
96
+ const items = useMemo(
97
+ () =>
98
+ NAV.map(entry => ({
99
+ name: entry.id,
100
+ label: t(entry.labelKey, { fallback: entry.id }),
101
+ icon: entry.icon,
102
+ })),
103
+ [t]
104
+ );
105
+ // The sidebar search filters the navigation here; an app with records to find can point the
106
+ // same field at them instead, as an app-wide search.
107
+ const visibleItems = useFuzzySearch(items, navQuery, { keys: ['label'] });
108
+ const searchPlaceholder = t('nav.search', { fallback: 'Search Views…' });
118
109
 
119
110
  // Mirror the section switcher into the OS top menu: each view is a checkable command, so the
120
111
  // active section can be changed from the host menu bar (next to About / Version) — not just
@@ -161,22 +152,30 @@ export function AppShell() {
161
152
  const activeNav = NAV.find(entry => entry.id === view);
162
153
  const pageTitle = activeNav ? t(activeNav.titleKey, { fallback: activeNav.id }) : undefined;
163
154
 
164
- const header = (
165
- <div className="flex items-center gap-2 px-2 py-1">
166
- <BardiocLogoIcon className="size-5" />
167
- <span className="truncate text-sm font-semibold">
168
- {t('app.title', { fallback: 'Weather Dashboard' })}
169
- </span>
170
- </div>
171
- );
172
-
173
155
  return (
174
156
  <SidebarContainedProvider defaultCollapsed={false}>
175
157
  <div className="bg-background text-foreground @container/app flex h-full min-h-0">
176
158
  <SidebarBasic
177
- header={header}
159
+ // No logo or app name here: the OS window frame already shows both.
160
+ header={
161
+ <SidebarSearch
162
+ value={navQuery}
163
+ onChange={setNavQuery}
164
+ placeholder={searchPlaceholder}
165
+ collapsedLabel={searchPlaceholder}
166
+ shortcut={{ key: 'f' }}
167
+ size="sm"
168
+ showClearButton
169
+ />
170
+ }
171
+ footer={
172
+ <>
173
+ {import.meta.env.DEV && <DebugMenu />}
174
+ <SidebarContainedTrigger />
175
+ </>
176
+ }
178
177
  rail={false}
179
- items={items}
178
+ items={visibleItems}
180
179
  selectedItem={view}
181
180
  onItemSelected={item => {
182
181
  if (!item) return;
@@ -190,8 +189,10 @@ export function AppShell() {
190
189
  actionsClassName="gap-2"
191
190
  sticky
192
191
  className="min-w-0"
192
+ // Grows the body to the page height, so a view can fill it (the explorer's panel does).
193
+ contentClassName="flex-1"
193
194
  >
194
- <ViewContent view={view} />
195
+ <ViewContent view={view} title={pageTitle} />
195
196
  </Page>
196
197
  </div>
197
198
  <AboutDialog open={aboutOpen} onOpenChange={setAboutOpen} />
@@ -0,0 +1,86 @@
1
+ import {
2
+ DropdownMenuCheckboxItem,
3
+ DropdownMenuItem,
4
+ DropdownMenuLabel,
5
+ SidebarMenuBadge,
6
+ SidebarMenuButton,
7
+ useContainedSidebarState,
8
+ } from '@bardioc/ui/components';
9
+ import { useDebugMode } from '@bardioc/ui/lib';
10
+ import * as Shadcn from '@bardioc/ui/shadcn';
11
+ import { Bug } from 'lucide-react';
12
+ import { useTranslation } from '../i18n';
13
+ import { PREVIEW_STATES, usePreviewState, type PreviewState } from '../preview-state';
14
+
15
+ const PREVIEW_STATE_FALLBACKS: Record<PreviewState, string> = {
16
+ live: 'Live',
17
+ mock: 'Mock Data',
18
+ loading: 'Loading',
19
+ empty: 'Empty',
20
+ error: 'Error',
21
+ };
22
+
23
+ // Sidebar footer entry for development: the same debug flag the OS dock toggles (it adds debug
24
+ // rows to error states), plus a preview state: Loading, Empty or Error for every view, or Mock Data
25
+ // for views that otherwise show only live data.
26
+ export function DebugMenu() {
27
+ const { t } = useTranslation();
28
+ const { isCollapsed } = useContainedSidebarState();
29
+ const [debugMode, setDebugMode] = useDebugMode();
30
+ const { previewState, setPreviewState } = usePreviewState();
31
+ const label = t('debug.menu', { fallback: 'Debug' });
32
+ const stateLabel = (state: PreviewState) =>
33
+ t(`debug.state.${state}`, { fallback: PREVIEW_STATE_FALLBACKS[state] });
34
+ const previewing = previewState !== 'live';
35
+ // A forced state marks the entry active and names the state in a badge; collapsed hides menu
36
+ // badges, so the tooltip names it there.
37
+ const collapsedLabel = previewing
38
+ ? t('debug.menuWithState', {
39
+ fallback: 'Debug: {state}',
40
+ params: { state: stateLabel(previewState) },
41
+ })
42
+ : label;
43
+
44
+ return (
45
+ <Shadcn.SidebarMenu>
46
+ <Shadcn.SidebarMenuItem>
47
+ <Shadcn.DropdownMenu>
48
+ <Shadcn.DropdownMenuTrigger asChild>
49
+ <SidebarMenuButton
50
+ isActive={previewing}
51
+ tooltip={{ hidden: !isCollapsed, children: collapsedLabel }}
52
+ aria-label={isCollapsed ? collapsedLabel : undefined}
53
+ >
54
+ <Bug aria-hidden="true" />
55
+ {!isCollapsed && <span>{label}</span>}
56
+ </SidebarMenuButton>
57
+ </Shadcn.DropdownMenuTrigger>
58
+ <Shadcn.DropdownMenuContent side="right" align="end">
59
+ <DropdownMenuCheckboxItem
60
+ checked={debugMode}
61
+ onCheckedChange={checked => setDebugMode(checked === true)}
62
+ >
63
+ {t('debug.showInfo', { fallback: 'Show Debug Info' })}
64
+ </DropdownMenuCheckboxItem>
65
+ <Shadcn.DropdownMenuSeparator />
66
+ <DropdownMenuLabel>
67
+ {t('debug.previewState', { fallback: 'Preview State' })}
68
+ </DropdownMenuLabel>
69
+ {PREVIEW_STATES.map(state => (
70
+ <DropdownMenuItem
71
+ key={state}
72
+ variant="check-icon"
73
+ reserveCheckSpace
74
+ checked={previewState === state}
75
+ onSelect={() => setPreviewState(state)}
76
+ >
77
+ {stateLabel(state)}
78
+ </DropdownMenuItem>
79
+ ))}
80
+ </Shadcn.DropdownMenuContent>
81
+ </Shadcn.DropdownMenu>
82
+ {previewing && <SidebarMenuBadge>{stateLabel(previewState)}</SidebarMenuBadge>}
83
+ </Shadcn.SidebarMenuItem>
84
+ </Shadcn.SidebarMenu>
85
+ );
86
+ }
@@ -12,7 +12,7 @@ export function Header() {
12
12
 
13
13
  return (
14
14
  <>
15
- {state?.stations && (
15
+ {state && state.stations.length > 0 && (
16
16
  <LocationSelect
17
17
  stations={state.stations}
18
18
  value={state.activeStationId}
@@ -23,14 +23,16 @@ export function Header() {
23
23
  useI18n={useI18n}
24
24
  supportedLocales={[...SUPPORTED_LOCALES]}
25
25
  flags={{ en: 'EN', de: 'DE' }}
26
+ size="icon-sm"
26
27
  />
27
28
  <Button
28
- className="gap-2"
29
29
  size="sm"
30
30
  onClick={() => void refresh()}
31
- disabled={syncing || !state}
31
+ disabled={!state}
32
+ isLoading={syncing}
33
+ stableWidth
32
34
  >
33
- <RefreshCw className={`size-4 ${syncing ? 'animate-spin' : ''}`} />
35
+ <RefreshCw />
34
36
  {t('header.sync', { fallback: 'Sync' })}
35
37
  </Button>
36
38
  </>
@@ -0,0 +1,69 @@
1
+ import { Button, Empty, SkeletonWrapper } from '@bardioc/ui/components';
2
+ import { TriangleAlert } from 'lucide-react';
3
+ import { OverviewView } from '../climate/overview-view';
4
+ import { StationsView } from '../climate/stations-view';
5
+ import type { ViewId } from '../climate/types';
6
+ import { LiveEventsView } from '../events/live-events-view';
7
+ import { ExplorerView } from '../graph/explorer-view';
8
+ import { useTranslation } from '../i18n';
9
+ import { usePreviewState } from '../preview-state';
10
+ import { ProfileView } from '../profile/profile-view';
11
+
12
+ function View({ view }: Readonly<{ view: ViewId }>) {
13
+ switch (view) {
14
+ case 'overview':
15
+ return <OverviewView />;
16
+ case 'stations':
17
+ return <StationsView />;
18
+ case 'explorer':
19
+ return <ExplorerView />;
20
+ case 'events':
21
+ return <LiveEventsView />;
22
+ case 'profile':
23
+ return <ProfileView />;
24
+ default:
25
+ return null;
26
+ }
27
+ }
28
+
29
+ // The active view, or the state the debug menu previews: Loading skeletonizes the view's own
30
+ // layout, Error shows the shared error state (with debug rows while "Show Debug Info" is on),
31
+ // and Empty reaches the views through their data (see `DashboardProvider`).
32
+ export function ViewContent({ view, title }: Readonly<{ view: ViewId; title?: string }>) {
33
+ const { t } = useTranslation();
34
+ const { previewState, setPreviewState } = usePreviewState();
35
+
36
+ switch (previewState) {
37
+ case 'loading':
38
+ return (
39
+ <SkeletonWrapper loading>
40
+ <View view={view} />
41
+ </SkeletonWrapper>
42
+ );
43
+ case 'error':
44
+ return (
45
+ <Empty
46
+ icon={TriangleAlert}
47
+ iconVariant="chip"
48
+ title={t('preview.errorTitle', { fallback: 'Could Not Load Data' })}
49
+ description={t('preview.errorDescription', {
50
+ fallback: 'A simulated error from the preview state in the debug menu.',
51
+ })}
52
+ debugRows={[
53
+ { label: t('preview.debugView', { fallback: 'View' }), value: title ?? view },
54
+ {
55
+ label: t('debug.previewState', { fallback: 'Preview State' }),
56
+ value: t('debug.state.error', { fallback: 'Error' }),
57
+ },
58
+ ]}
59
+ actions={
60
+ <Button size="sm" onClick={() => setPreviewState('live')}>
61
+ {t('preview.retry', { fallback: 'Retry' })}
62
+ </Button>
63
+ }
64
+ />
65
+ );
66
+ default:
67
+ return <View view={view} />;
68
+ }
69
+ }
@@ -5,3 +5,9 @@ node_modules
5
5
  *.zip
6
6
  .linked/
7
7
  .DS_Store
8
+
9
+ # Generated by `npm run compliance:all`, shipped inside the bundle.
10
+ public/sbom.cdx.json
11
+ public/license-compliance.json
12
+ public/security-audit.json
13
+ public/compliance-summary.json
@@ -6,7 +6,11 @@
6
6
  "scripts": {
7
7
  "dev": "vite",
8
8
  "build": "vue-tsc -b && vite build && node scripts/stamp-manifest.mjs dist",
9
- "bundle": "vue-tsc -b && vite build && node scripts/stamp-manifest.mjs dist && cd dist && zip -r ../{{APP_NAME}}.zip .",
9
+ "bundle": "npm run compliance:all && vue-tsc -b && vite build && node scripts/stamp-manifest.mjs dist && cd dist && zip -r ../{{APP_NAME}}.zip .",
10
+ "sbom": "cyclonedx-npm --omit dev --output-format json --output-reproducible --output-file public/sbom.cdx.json",
11
+ "compliance": "node scripts/compliance.mjs public",
12
+ "audit:security": "node scripts/audit.mjs public",
13
+ "compliance:all": "npm run sbom && npm run compliance && npm run audit:security && node scripts/compliance-summary.mjs public",
10
14
  "preview": "vite preview",
11
15
  "check-types": "vue-tsc --noEmit",
12
16
  "lint": "eslint .",
@@ -18,16 +22,18 @@
18
22
  "release:publish": "changeset publish"
19
23
  },
20
24
  "dependencies": {
21
- "@bardioc/app-sdk": "^1.0.1",
25
+ "@bardioc/app-sdk": "^1.0.3",
22
26
  "vue": "^3.5.16"
23
27
  },
24
28
  "devDependencies": {
25
29
  "@bardioc/configs": "0.2.1",
26
30
  "@changesets/cli": "^2.31.0",
31
+ "@cyclonedx/cyclonedx-npm": "^6.0.1",
27
32
  "@tailwindcss/vite": "^4.3.0",
28
33
  "@types/node": "^22.19.19",
29
34
  "@vitejs/plugin-vue": "^6.0.8",
30
35
  "eslint": "^10.0.0",
36
+ "license-checker-rseidelsohn": "^5.0.1",
31
37
  "prettier": "^3.6.2",
32
38
  "tailwindcss": "4.3.0",
33
39
  "typescript": "6.0.3",