Dev Tools improvements (#247)
* Refactor environment variables UI and update validation logic Enhanced the environment variables page layout for better responsiveness and structure by introducing new components and styles. Added `EnvListDisplay` for grouped variable display and adjusted several UI elements for clarity and consistency. Updated `NEXT_PUBLIC_SENTRY_ENVIRONMENT` validation to make it optional, aligning with updated requirements. * Add environment variable validation and enhance page headers Introduces robust validation for environment variables, ensuring correctness and contextual dependency checks. Updates page headers with titles and detailed descriptions for better usability and clarity. * Refactor variable page layout and improve code readability Rearranged className attributes in JSX for consistency and readability. Refactored map and enum validation logic for better formatting and maintainability. Applied minor corrections to types and formatting in other components. * Refactor styles and simplify component logic Updated badge variants to streamline styles and removed redundant hover states. Simplified logic in email page by extracting breadcrumb values and optimizing title rendering. Adjusted environment variables manager layout for cleaner rendering and removed unnecessary elements. * Add real-time translation updates with RxJS and UI improvements Introduced a Subject with debounce mechanism for handling translation updates, enhancing real-time editing in the translations comparison module. Improved UI components, including conditional rendering, better input handling, and layout adjustments. Implemented a server action for updating translations and streamlined type definitions in the emails page. * Enhance environment variable copying functionality and improve user feedback Updated the environment variables manager to copy structured environment variable data to the clipboard, improving usability. Adjusted toast notifications to provide clearer success and error messages during the copy process. Additionally, fixed a minor issue in the translations comparison component by ensuring proper filtering of keys based on the search input. * Add AI translation functionality and update dependencies Implemented a new action for translating missing strings using AI, enhancing the translations comparison component. Introduced a loading state during translation and improved error handling for translation updates. Updated package dependencies, including the addition of '@ai-sdk/openai' and 'ai' to facilitate AI-driven translations. Enhanced UI components for better user experience and streamlined translation management.
This commit is contained in:
committed by
GitHub
parent
cea46b06a1
commit
76bfeddd32
@@ -34,24 +34,23 @@ export default async function EmailPage(props: EmailPageProps) {
|
|||||||
const template = await loadEmailTemplate(id);
|
const template = await loadEmailTemplate(id);
|
||||||
const emailSettings = await getEmailSettings(mode);
|
const emailSettings = await getEmailSettings(mode);
|
||||||
|
|
||||||
|
const values: Record<string, string> = {
|
||||||
|
emails: 'Emails',
|
||||||
|
'invite-email': 'Invite Email',
|
||||||
|
'account-delete-email': 'Account Delete Email',
|
||||||
|
'confirm-email': 'Confirm Email',
|
||||||
|
'change-email-address-email': 'Change Email Address Email',
|
||||||
|
'reset-password-email': 'Reset Password Email',
|
||||||
|
'magic-link-email': 'Magic Link Email',
|
||||||
|
'otp-email': 'OTP Email',
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Page style={'custom'}>
|
<Page style={'custom'}>
|
||||||
<PageHeader
|
<PageHeader
|
||||||
displaySidebarTrigger={false}
|
displaySidebarTrigger={false}
|
||||||
description={
|
title={values[id]}
|
||||||
<AppBreadcrumbs
|
description={<AppBreadcrumbs values={values} />}
|
||||||
values={{
|
|
||||||
emails: 'Emails',
|
|
||||||
'invite-email': 'Invite Email',
|
|
||||||
'account-delete-email': 'Account Delete Email',
|
|
||||||
'confirm-email': 'Confirm Email',
|
|
||||||
'change-email-address-email': 'Change Email Address Email',
|
|
||||||
'reset-password-email': 'Reset Password Email',
|
|
||||||
'magic-link-email': 'Magic Link Email',
|
|
||||||
'otp-email': 'OTP Email',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
<EnvModeSelector mode={mode} />
|
<EnvModeSelector mode={mode} />
|
||||||
</PageHeader>
|
</PageHeader>
|
||||||
|
|||||||
@@ -15,7 +15,11 @@ export const metadata = {
|
|||||||
export default async function EmailsPage() {
|
export default async function EmailsPage() {
|
||||||
return (
|
return (
|
||||||
<Page style={'custom'}>
|
<Page style={'custom'}>
|
||||||
<PageHeader displaySidebarTrigger={false} description="Emails" />
|
<PageHeader
|
||||||
|
displaySidebarTrigger={false}
|
||||||
|
title="Emails"
|
||||||
|
description={'Manage your application Email templates'}
|
||||||
|
/>
|
||||||
|
|
||||||
<PageBody className={'gap-y-8'}>
|
<PageBody className={'gap-y-8'}>
|
||||||
<div className={'flex flex-col space-y-4'}>
|
<div className={'flex flex-col space-y-4'}>
|
||||||
|
|||||||
@@ -31,25 +31,32 @@ class ConnectivityService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await fetch(`${url}/auth/v1/health`, {
|
try {
|
||||||
headers: {
|
const response = await fetch(`${url}/auth/v1/health`, {
|
||||||
apikey: anonKey,
|
headers: {
|
||||||
Authorization: `Bearer ${anonKey}`,
|
apikey: anonKey,
|
||||||
},
|
Authorization: `Bearer ${anonKey}`,
|
||||||
});
|
},
|
||||||
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
|
return {
|
||||||
|
status: 'error' as const,
|
||||||
|
message:
|
||||||
|
'Failed to connect to Supabase. The Supabase Anon Key or URL is not valid.',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
status: 'success' as const,
|
||||||
|
message: 'Connected to Supabase',
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
return {
|
return {
|
||||||
status: 'error' as const,
|
status: 'error' as const,
|
||||||
message:
|
message: `Failed to connect to Supabase. ${error}`,
|
||||||
'Failed to connect to Supabase. The Supabase Anon Key or URL is not valid.',
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
|
||||||
status: 'success' as const,
|
|
||||||
message: 'Connected to Supabase',
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async checkSupabaseAdminConnectivity() {
|
async checkSupabaseAdminConnectivity() {
|
||||||
@@ -85,35 +92,42 @@ class ConnectivityService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await fetch(endpoint, {
|
try {
|
||||||
headers: {
|
const response = await fetch(endpoint, {
|
||||||
apikey,
|
headers: {
|
||||||
Authorization: `Bearer ${adminKey}`,
|
apikey,
|
||||||
},
|
Authorization: `Bearer ${adminKey}`,
|
||||||
});
|
},
|
||||||
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
|
return {
|
||||||
|
status: 'error' as const,
|
||||||
|
message:
|
||||||
|
'Failed to connect to Supabase Admin. The Supabase Service Role Key is not valid.',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (data.length === 0) {
|
||||||
|
return {
|
||||||
|
status: 'error' as const,
|
||||||
|
message:
|
||||||
|
'No accounts found in Supabase Admin. The data may not be seeded. Please run `pnpm run supabase:web:reset` to reset the database.',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
status: 'success' as const,
|
||||||
|
message: 'Connected to Supabase Admin',
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
return {
|
return {
|
||||||
status: 'error' as const,
|
status: 'error' as const,
|
||||||
message:
|
message: `Failed to connect to Supabase Admin. ${error}`,
|
||||||
'Failed to connect to Supabase Admin. The Supabase Service Role Key is not valid.',
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
|
|
||||||
if (data.length === 0) {
|
|
||||||
return {
|
|
||||||
status: 'error' as const,
|
|
||||||
message:
|
|
||||||
'No accounts found in Supabase Admin. The data may not be seeded. Please run `pnpm run supabase:web:reset` to reset the database.',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
status: 'success' as const,
|
|
||||||
message: 'Connected to Supabase Admin',
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async checkStripeWebhookEndpoints() {
|
async checkStripeWebhookEndpoints() {
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ export default async function DashboardPage(props: DashboardPageProps) {
|
|||||||
<Page style={'custom'}>
|
<Page style={'custom'}>
|
||||||
<PageHeader
|
<PageHeader
|
||||||
displaySidebarTrigger={false}
|
displaySidebarTrigger={false}
|
||||||
|
title={'Dev Tool'}
|
||||||
description={'Check the status of your Supabase and Stripe services'}
|
description={'Check the status of your Supabase and Stripe services'}
|
||||||
>
|
>
|
||||||
<EnvModeSelector mode={mode} />
|
<EnvModeSelector mode={mode} />
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useState } from 'react';
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
|
|
||||||
import { ChevronDownIcon } from 'lucide-react';
|
import { ChevronDownIcon, Loader2Icon } from 'lucide-react';
|
||||||
|
import { Subject, debounceTime } from 'rxjs';
|
||||||
|
|
||||||
import { Button } from '@kit/ui/button';
|
import { Button } from '@kit/ui/button';
|
||||||
import {
|
import {
|
||||||
@@ -11,6 +12,7 @@ import {
|
|||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from '@kit/ui/dropdown-menu';
|
} from '@kit/ui/dropdown-menu';
|
||||||
|
import { If } from '@kit/ui/if';
|
||||||
import { Input } from '@kit/ui/input';
|
import { Input } from '@kit/ui/input';
|
||||||
import {
|
import {
|
||||||
Select,
|
Select,
|
||||||
@@ -19,6 +21,7 @@ import {
|
|||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
SelectValue,
|
SelectValue,
|
||||||
} from '@kit/ui/select';
|
} from '@kit/ui/select';
|
||||||
|
import { toast } from '@kit/ui/sonner';
|
||||||
import {
|
import {
|
||||||
Table,
|
Table,
|
||||||
TableBody,
|
TableBody,
|
||||||
@@ -30,6 +33,10 @@ import {
|
|||||||
import { cn } from '@kit/ui/utils';
|
import { cn } from '@kit/ui/utils';
|
||||||
|
|
||||||
import { defaultI18nNamespaces } from '../../../../web/lib/i18n/i18n.settings';
|
import { defaultI18nNamespaces } from '../../../../web/lib/i18n/i18n.settings';
|
||||||
|
import {
|
||||||
|
translateWithAIAction,
|
||||||
|
updateTranslationAction,
|
||||||
|
} from '../lib/server-actions';
|
||||||
import type { TranslationData, Translations } from '../lib/translations-loader';
|
import type { TranslationData, Translations } from '../lib/translations-loader';
|
||||||
|
|
||||||
function flattenTranslations(
|
function flattenTranslations(
|
||||||
@@ -58,31 +65,37 @@ export function TranslationsComparison({
|
|||||||
translations: Translations;
|
translations: Translations;
|
||||||
}) {
|
}) {
|
||||||
const [search, setSearch] = useState('');
|
const [search, setSearch] = useState('');
|
||||||
const [selectedLocales, setSelectedLocales] = useState<Set<string>>();
|
const [isTranslating, setIsTranslating] = useState(false);
|
||||||
|
|
||||||
const [selectedNamespace, setSelectedNamespace] = useState(
|
const [selectedNamespace, setSelectedNamespace] = useState(
|
||||||
defaultI18nNamespaces[0] as string,
|
defaultI18nNamespaces[0] as string,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Create RxJS Subject for handling translation updates
|
||||||
|
const subject$ = useMemo(
|
||||||
|
() =>
|
||||||
|
new Subject<{
|
||||||
|
locale: string;
|
||||||
|
namespace: string;
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}>(),
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
const locales = Object.keys(translations);
|
const locales = Object.keys(translations);
|
||||||
|
|
||||||
if (locales.length === 0) {
|
|
||||||
return <div>No translations found</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
const baseLocale = locales[0]!;
|
const baseLocale = locales[0]!;
|
||||||
|
|
||||||
// Initialize selected locales if not set
|
const [selectedLocales, setSelectedLocales] = useState<Set<string>>(
|
||||||
if (!selectedLocales) {
|
new Set(locales),
|
||||||
setSelectedLocales(new Set(locales));
|
);
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Flatten translations for the selected namespace
|
// Flatten translations for the selected namespace
|
||||||
const flattenedTranslations: FlattenedTranslations = {};
|
const flattenedTranslations: FlattenedTranslations = {};
|
||||||
|
|
||||||
for (const locale of locales) {
|
for (const locale of locales) {
|
||||||
const namespaceData = translations[locale]?.[selectedNamespace];
|
const namespaceData = translations[locale]?.[selectedNamespace];
|
||||||
|
|
||||||
if (namespaceData) {
|
if (namespaceData) {
|
||||||
flattenedTranslations[locale] = flattenTranslations(namespaceData);
|
flattenedTranslations[locale] = flattenTranslations(namespaceData);
|
||||||
} else {
|
} else {
|
||||||
@@ -105,14 +118,6 @@ export function TranslationsComparison({
|
|||||||
selectedLocales.has(locale),
|
selectedLocales.has(locale),
|
||||||
);
|
);
|
||||||
|
|
||||||
const copyTranslation = async (text: string) => {
|
|
||||||
try {
|
|
||||||
await navigator.clipboard.writeText(text);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to copy text:', error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const toggleLocale = (locale: string) => {
|
const toggleLocale = (locale: string) => {
|
||||||
const newSelectedLocales = new Set(selectedLocales);
|
const newSelectedLocales = new Set(selectedLocales);
|
||||||
|
|
||||||
@@ -127,58 +132,152 @@ export function TranslationsComparison({
|
|||||||
setSelectedLocales(newSelectedLocales);
|
setSelectedLocales(newSelectedLocales);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleTranslateWithAI = useCallback(async () => {
|
||||||
|
try {
|
||||||
|
setIsTranslating(true);
|
||||||
|
|
||||||
|
// Get missing translations for the selected namespace
|
||||||
|
const missingTranslations: Record<string, string> = {};
|
||||||
|
const baseTranslations = flattenedTranslations[baseLocale] ?? {};
|
||||||
|
|
||||||
|
for (const locale of visibleLocales) {
|
||||||
|
if (locale === baseLocale) continue;
|
||||||
|
|
||||||
|
const localeTranslations = flattenedTranslations[locale] ?? {};
|
||||||
|
|
||||||
|
for (const [key, value] of Object.entries(baseTranslations)) {
|
||||||
|
if (!localeTranslations[key]) {
|
||||||
|
missingTranslations[key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Object.keys(missingTranslations).length > 0) {
|
||||||
|
await translateWithAIAction({
|
||||||
|
sourceLocale: baseLocale,
|
||||||
|
targetLocale: locale,
|
||||||
|
namespace: selectedNamespace,
|
||||||
|
translations: missingTranslations,
|
||||||
|
});
|
||||||
|
|
||||||
|
toast.success(`Translated missing strings to ${locale}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
toast.error('Failed to translate: ' + (error as Error).message);
|
||||||
|
} finally {
|
||||||
|
setIsTranslating(false);
|
||||||
|
}
|
||||||
|
}, [flattenedTranslations, baseLocale, visibleLocales, selectedNamespace]);
|
||||||
|
|
||||||
|
// Calculate if there are any missing translations
|
||||||
|
const hasMissingTranslations = useMemo(() => {
|
||||||
|
if (!flattenedTranslations || !baseLocale || !visibleLocales) return false;
|
||||||
|
|
||||||
|
const baseTranslations = flattenedTranslations[baseLocale] ?? {};
|
||||||
|
|
||||||
|
return visibleLocales.some((locale) => {
|
||||||
|
if (locale === baseLocale) return false;
|
||||||
|
|
||||||
|
const localeTranslations = flattenedTranslations[locale] ?? {};
|
||||||
|
|
||||||
|
return Object.keys(baseTranslations).some(
|
||||||
|
(key) => !localeTranslations[key],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}, [flattenedTranslations, baseLocale, visibleLocales]);
|
||||||
|
|
||||||
|
// Set up subscription to handle debounced updates
|
||||||
|
useEffect(() => {
|
||||||
|
const subscription = subject$.pipe(debounceTime(500)).subscribe((props) => {
|
||||||
|
updateTranslationAction(props)
|
||||||
|
.then(() => {
|
||||||
|
toast.success(`Updated translation for ${props.key}`);
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
toast.error(`Failed to update translation: ${err.message}`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => subscription.unsubscribe();
|
||||||
|
}, [subject$]);
|
||||||
|
|
||||||
|
if (locales.length === 0) {
|
||||||
|
return <div>No translations found</div>;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4 pb-24">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex w-full items-center">
|
||||||
<div className="flex items-center gap-2.5">
|
<div className="flex w-full items-center justify-between gap-2.5">
|
||||||
<Input
|
<div className="flex items-center gap-2.5">
|
||||||
type="search"
|
<Input
|
||||||
placeholder="Search translations..."
|
type="search"
|
||||||
value={search}
|
placeholder="Search translations..."
|
||||||
onChange={(e) => setSearch(e.target.value)}
|
value={search}
|
||||||
className="max-w-sm"
|
onChange={(e) => setSearch(e.target.value)}
|
||||||
/>
|
className="max-w-sm"
|
||||||
|
/>
|
||||||
|
|
||||||
<DropdownMenu>
|
<If condition={locales.length > 1}>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenu>
|
||||||
<Button variant="outline" className="ml-auto">
|
<DropdownMenuTrigger asChild>
|
||||||
Select Languages
|
<Button variant="outline" className="ml-auto">
|
||||||
<ChevronDownIcon className="ml-2 h-4 w-4" />
|
Select Languages
|
||||||
</Button>
|
<ChevronDownIcon className="ml-2 h-4 w-4" />
|
||||||
</DropdownMenuTrigger>
|
</Button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
|
||||||
<DropdownMenuContent align="end" className="w-[200px]">
|
<DropdownMenuContent align="end" className="w-[200px]">
|
||||||
{locales.map((locale) => (
|
{locales.map((locale) => (
|
||||||
<DropdownMenuCheckboxItem
|
<DropdownMenuCheckboxItem
|
||||||
key={locale}
|
key={locale}
|
||||||
checked={selectedLocales.has(locale)}
|
checked={selectedLocales.has(locale)}
|
||||||
onCheckedChange={() => toggleLocale(locale)}
|
onCheckedChange={() => toggleLocale(locale)}
|
||||||
disabled={
|
disabled={
|
||||||
selectedLocales.size === 1 && selectedLocales.has(locale)
|
selectedLocales.size === 1 &&
|
||||||
}
|
selectedLocales.has(locale)
|
||||||
>
|
}
|
||||||
{locale}
|
>
|
||||||
</DropdownMenuCheckboxItem>
|
{locale}
|
||||||
))}
|
</DropdownMenuCheckboxItem>
|
||||||
</DropdownMenuContent>
|
))}
|
||||||
</DropdownMenu>
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
</If>
|
||||||
|
|
||||||
<Select
|
<Select
|
||||||
value={selectedNamespace}
|
value={selectedNamespace}
|
||||||
onValueChange={setSelectedNamespace}
|
onValueChange={setSelectedNamespace}
|
||||||
>
|
>
|
||||||
<SelectTrigger className="w-[180px]">
|
<SelectTrigger className="w-[180px]">
|
||||||
<SelectValue placeholder="Select namespace" />
|
<SelectValue placeholder="Select namespace" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
|
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
{defaultI18nNamespaces.map((namespace: string) => (
|
{defaultI18nNamespaces.map((namespace: string) => (
|
||||||
<SelectItem key={namespace} value={namespace}>
|
<SelectItem key={namespace} value={namespace}>
|
||||||
{namespace}
|
{namespace}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Button
|
||||||
|
onClick={handleTranslateWithAI}
|
||||||
|
disabled={isTranslating || !hasMissingTranslations}
|
||||||
|
>
|
||||||
|
{isTranslating ? (
|
||||||
|
<>
|
||||||
|
<Loader2Icon className="mr-2 h-4 w-4 animate-spin" />
|
||||||
|
Translating...
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
'Translate missing with AI'
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -196,7 +295,7 @@ export function TranslationsComparison({
|
|||||||
<TableBody>
|
<TableBody>
|
||||||
{filteredKeys.map((key) => (
|
{filteredKeys.map((key) => (
|
||||||
<TableRow key={key}>
|
<TableRow key={key}>
|
||||||
<TableCell className="font-mono text-sm">
|
<TableCell width={350} className="text-sm">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<span>{key}</span>
|
<span>{key}</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -222,11 +321,33 @@ export function TranslationsComparison({
|
|||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<span>
|
<Input
|
||||||
{value || (
|
defaultValue={value || ''}
|
||||||
<span className="text-destructive">Missing</span>
|
onChange={(e) => {
|
||||||
)}
|
const value = e.target.value.trim();
|
||||||
</span>
|
|
||||||
|
if (value === '') {
|
||||||
|
toast.error('Translation cannot be empty');
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value === baseValue) {
|
||||||
|
toast.info('Translation is the same as base');
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
subject$.next({
|
||||||
|
locale,
|
||||||
|
namespace: selectedNamespace,
|
||||||
|
key,
|
||||||
|
value,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
className="w-full font-mono text-sm"
|
||||||
|
placeholder={isMissing ? 'Missing translation' : ''}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
);
|
);
|
||||||
|
|||||||
161
apps/dev-tool/app/translations/lib/server-actions.ts
Normal file
161
apps/dev-tool/app/translations/lib/server-actions.ts
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
'use server';
|
||||||
|
|
||||||
|
import { revalidatePath } from 'next/cache';
|
||||||
|
|
||||||
|
import { openai } from '@ai-sdk/openai';
|
||||||
|
import { generateText } from 'ai';
|
||||||
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
||||||
|
import { resolve } from 'node:url';
|
||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
import { getLogger } from '@kit/shared/logger';
|
||||||
|
|
||||||
|
const Schema = z.object({
|
||||||
|
locale: z.string().min(1),
|
||||||
|
namespace: z.string().min(1),
|
||||||
|
key: z.string().min(1),
|
||||||
|
value: z.string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const TranslateSchema = z.object({
|
||||||
|
sourceLocale: z.string(),
|
||||||
|
targetLocale: z.string(),
|
||||||
|
namespace: z.string(),
|
||||||
|
translations: z.record(z.string(), z.string()),
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update a translation value in the specified locale and namespace.
|
||||||
|
* @param props
|
||||||
|
*/
|
||||||
|
export async function updateTranslationAction(props: z.infer<typeof Schema>) {
|
||||||
|
// Validate the input
|
||||||
|
const { locale, namespace, key, value } = Schema.parse(props);
|
||||||
|
|
||||||
|
const root = resolve(process.cwd(), '..');
|
||||||
|
const filePath = `${root}apps/web/public/locales/${locale}/${namespace}.json`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Read the current translations file
|
||||||
|
const translationsFile = readFileSync(filePath, 'utf-8');
|
||||||
|
const translations = JSON.parse(translationsFile) as Record<string, any>;
|
||||||
|
|
||||||
|
// Update the nested key value
|
||||||
|
const keys = key.split('.') as string[];
|
||||||
|
let current = translations;
|
||||||
|
|
||||||
|
// Navigate through nested objects until the second-to-last key
|
||||||
|
for (let i = 0; i < keys.length - 1; i++) {
|
||||||
|
const currentKey = keys[i] as string;
|
||||||
|
|
||||||
|
if (!current[currentKey]) {
|
||||||
|
current[currentKey] = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
current = current[currentKey];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the value at the final key
|
||||||
|
const finalKey = keys[keys.length - 1] as string;
|
||||||
|
current[finalKey] = value;
|
||||||
|
|
||||||
|
// Write the updated translations back to the file
|
||||||
|
writeFileSync(filePath, JSON.stringify(translations, null, 2), 'utf-8');
|
||||||
|
|
||||||
|
revalidatePath(`/translations`);
|
||||||
|
|
||||||
|
return { success: true };
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to update translation:', error);
|
||||||
|
throw new Error('Failed to update translation');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const translateWithAIAction = async (
|
||||||
|
data: z.infer<typeof TranslateSchema>,
|
||||||
|
) => {
|
||||||
|
const logger = await getLogger();
|
||||||
|
|
||||||
|
z.string().min(1).parse(process.env.OPENAI_API_KEY);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { sourceLocale, targetLocale, namespace, translations } =
|
||||||
|
TranslateSchema.parse(data);
|
||||||
|
|
||||||
|
// if the path does not exist, create it using an empty object
|
||||||
|
const root = resolve(process.cwd(), '..');
|
||||||
|
const folderPath = `${root}apps/web/public/locales/${targetLocale}`;
|
||||||
|
|
||||||
|
if (!existsSync(folderPath)) {
|
||||||
|
// create the directory if it doesn't exist
|
||||||
|
mkdirSync(folderPath, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
const filePath = `${folderPath}/${namespace}.json`;
|
||||||
|
|
||||||
|
if (!existsSync(filePath)) {
|
||||||
|
// create the file if it doesn't exist
|
||||||
|
writeFileSync(filePath, JSON.stringify({}, null, 2), 'utf-8');
|
||||||
|
}
|
||||||
|
|
||||||
|
const results: Record<string, string> = {};
|
||||||
|
|
||||||
|
// Process translations in batches of 5 for efficiency
|
||||||
|
const entries = Object.entries(translations);
|
||||||
|
const batches = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < entries.length; i += 5) {
|
||||||
|
batches.push(entries.slice(i, i + 5));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const batch of batches) {
|
||||||
|
const batchPromises = batch.map(async ([key, value]) => {
|
||||||
|
const prompt = `Translate the following text from ${sourceLocale} to ${targetLocale}. Maintain any placeholders (like {name} or %{count}) and HTML tags. Only return the translated text, nothing else.
|
||||||
|
|
||||||
|
Original text: ${value}`;
|
||||||
|
|
||||||
|
const MODEL_NAME = process.env.LLM_MODEL_NAME ?? 'gpt-4o-mini';
|
||||||
|
const model = openai(MODEL_NAME);
|
||||||
|
|
||||||
|
const { text } = await generateText({
|
||||||
|
model,
|
||||||
|
prompt,
|
||||||
|
temperature: 0.3,
|
||||||
|
maxTokens: 200,
|
||||||
|
});
|
||||||
|
|
||||||
|
return [key, text.trim()] as [string, string];
|
||||||
|
});
|
||||||
|
|
||||||
|
const batchResults = await Promise.all(batchPromises);
|
||||||
|
|
||||||
|
for (const [key, translation] of batchResults) {
|
||||||
|
results[key] = translation;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update each translation
|
||||||
|
for (const [key, translation] of Object.entries(results)) {
|
||||||
|
await updateTranslationAction({
|
||||||
|
locale: targetLocale,
|
||||||
|
namespace,
|
||||||
|
key,
|
||||||
|
value: translation,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info('AI translation completed', {
|
||||||
|
sourceLocale,
|
||||||
|
targetLocale,
|
||||||
|
namespace,
|
||||||
|
count: Object.keys(results).length,
|
||||||
|
});
|
||||||
|
|
||||||
|
revalidatePath('/translations');
|
||||||
|
|
||||||
|
return { success: true, translations: results };
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('AI translation failed', { error });
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -18,12 +18,9 @@ export default async function TranslationsPage() {
|
|||||||
<Page style={'custom'}>
|
<Page style={'custom'}>
|
||||||
<PageHeader
|
<PageHeader
|
||||||
displaySidebarTrigger={false}
|
displaySidebarTrigger={false}
|
||||||
|
title={'Translations'}
|
||||||
description={
|
description={
|
||||||
<AppBreadcrumbs
|
'Compare translations across different languages. Ensure consistency and accuracy in your translations.'
|
||||||
values={{
|
|
||||||
translations: 'Translations',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
161
apps/dev-tool/app/variables/components/dynamic-form-input.tsx
Normal file
161
apps/dev-tool/app/variables/components/dynamic-form-input.tsx
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
|
import { Input } from '@kit/ui/input';
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from '@kit/ui/select';
|
||||||
|
import { Switch } from '@kit/ui/switch';
|
||||||
|
import { Textarea } from '@kit/ui/textarea';
|
||||||
|
|
||||||
|
type ModelType =
|
||||||
|
| 'string'
|
||||||
|
| 'longString'
|
||||||
|
| 'number'
|
||||||
|
| 'boolean'
|
||||||
|
| 'enum'
|
||||||
|
| 'url'
|
||||||
|
| 'email';
|
||||||
|
|
||||||
|
interface DynamicFormInputProps {
|
||||||
|
type: ModelType;
|
||||||
|
value: string;
|
||||||
|
name: string;
|
||||||
|
onChange: (props: { name: string; value: string }) => void;
|
||||||
|
placeholder?: string;
|
||||||
|
enumValues?: Array<string | null>;
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function DynamicFormInput({
|
||||||
|
type,
|
||||||
|
value,
|
||||||
|
name,
|
||||||
|
onChange,
|
||||||
|
placeholder,
|
||||||
|
enumValues = [],
|
||||||
|
className,
|
||||||
|
}: DynamicFormInputProps) {
|
||||||
|
const handleInputChange = useCallback(
|
||||||
|
(e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
|
||||||
|
onChange({
|
||||||
|
name,
|
||||||
|
value: e.target.value,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[name, onChange],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleSwitchChange = useCallback(
|
||||||
|
(checked: boolean) => {
|
||||||
|
onChange({
|
||||||
|
name,
|
||||||
|
value: checked ? 'true' : 'false',
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[name, onChange],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleSelectChange = useCallback(
|
||||||
|
(value: string) => {
|
||||||
|
onChange({
|
||||||
|
name,
|
||||||
|
value: value === '' ? 'none' : value,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[name, onChange],
|
||||||
|
);
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case 'longString':
|
||||||
|
return (
|
||||||
|
<Textarea
|
||||||
|
defaultValue={value}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
placeholder={placeholder}
|
||||||
|
className={className}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
case 'number':
|
||||||
|
return (
|
||||||
|
<Input
|
||||||
|
type="number"
|
||||||
|
defaultValue={value}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
placeholder={placeholder}
|
||||||
|
className={className}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
case 'boolean':
|
||||||
|
return (
|
||||||
|
<label className="flex items-center gap-x-2">
|
||||||
|
<Switch
|
||||||
|
checked={value === 'true'}
|
||||||
|
onCheckedChange={handleSwitchChange}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<span className={'text-sm uppercase'}>
|
||||||
|
{value === 'true' ? 'True' : 'False'}
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
);
|
||||||
|
|
||||||
|
case 'url':
|
||||||
|
return (
|
||||||
|
<Input
|
||||||
|
type="url"
|
||||||
|
defaultValue={value}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
placeholder={placeholder}
|
||||||
|
className={className}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
case 'email':
|
||||||
|
return (
|
||||||
|
<Input
|
||||||
|
type="email"
|
||||||
|
defaultValue={value}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
placeholder={placeholder}
|
||||||
|
className={className}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
case 'enum':
|
||||||
|
return (
|
||||||
|
<Select
|
||||||
|
value={value === 'none' ? '' : value}
|
||||||
|
onValueChange={handleSelectChange}
|
||||||
|
>
|
||||||
|
<SelectTrigger className={className}>
|
||||||
|
<SelectValue placeholder={placeholder} />
|
||||||
|
</SelectTrigger>
|
||||||
|
|
||||||
|
<SelectContent>
|
||||||
|
{enumValues.map((enumValue) => (
|
||||||
|
<SelectItem key={enumValue} value={enumValue as string}>
|
||||||
|
{enumValue}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
);
|
||||||
|
|
||||||
|
default:
|
||||||
|
return (
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
defaultValue={value}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
placeholder={placeholder}
|
||||||
|
className={className}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -152,11 +152,18 @@ export function processEnvDefinitions(
|
|||||||
if (!variableMap[variable.key]) {
|
if (!variableMap[variable.key]) {
|
||||||
variableMap[variable.key] = {
|
variableMap[variable.key] = {
|
||||||
key: variable.key,
|
key: variable.key,
|
||||||
|
isVisible: true,
|
||||||
definitions: [],
|
definitions: [],
|
||||||
effectiveValue: variable.value,
|
effectiveValue: variable.value,
|
||||||
effectiveSource: variable.source,
|
effectiveSource: variable.source,
|
||||||
isOverridden: false,
|
isOverridden: false,
|
||||||
category: model ? model.category : 'Custom',
|
category: model ? model.category : 'Custom',
|
||||||
|
validation: {
|
||||||
|
success: true,
|
||||||
|
error: {
|
||||||
|
issues: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -212,6 +219,231 @@ export function processEnvDefinitions(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// after computing the effective values, we can check for errors
|
||||||
|
for (const key in variableMap) {
|
||||||
|
const model = envVariables.find((v) => key === v.name);
|
||||||
|
const varState = variableMap[key];
|
||||||
|
|
||||||
|
if (!varState) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let validation: {
|
||||||
|
success: boolean;
|
||||||
|
error: {
|
||||||
|
issues: string[];
|
||||||
|
};
|
||||||
|
} = { success: true, error: { issues: [] } };
|
||||||
|
|
||||||
|
if (model) {
|
||||||
|
const allVariables = Object.values(variableMap).reduce(
|
||||||
|
(acc, variable) => {
|
||||||
|
return {
|
||||||
|
...acc,
|
||||||
|
[variable.key]: variable.effectiveValue,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
{} as Record<string, string>,
|
||||||
|
);
|
||||||
|
|
||||||
|
// First check if it's required but missing
|
||||||
|
if (model.required && !varState.effectiveValue) {
|
||||||
|
validation = {
|
||||||
|
success: false,
|
||||||
|
error: {
|
||||||
|
issues: [
|
||||||
|
`This variable is required but missing from your environment files`,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
} else if (model.contextualValidation) {
|
||||||
|
// Then check contextual validation
|
||||||
|
const dependenciesMet = model.contextualValidation.dependencies.some(
|
||||||
|
(dep) => {
|
||||||
|
const dependencyValue = allVariables[dep.variable] ?? '';
|
||||||
|
|
||||||
|
return dep.condition(dependencyValue, allVariables);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (dependenciesMet) {
|
||||||
|
// Only check for missing value or run validation if dependencies are met
|
||||||
|
if (!varState.effectiveValue) {
|
||||||
|
const dependencyErrors = model.contextualValidation.dependencies
|
||||||
|
.map((dep) => {
|
||||||
|
const dependencyValue = allVariables[dep.variable] ?? '';
|
||||||
|
|
||||||
|
const shouldValidate = dep.condition(
|
||||||
|
dependencyValue,
|
||||||
|
allVariables,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (shouldValidate) {
|
||||||
|
const { success } = model.contextualValidation!.validate({
|
||||||
|
value: varState.effectiveValue,
|
||||||
|
variables: allVariables,
|
||||||
|
mode,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return dep.message;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
})
|
||||||
|
.filter((message): message is string => message !== null);
|
||||||
|
|
||||||
|
validation = {
|
||||||
|
success: dependencyErrors.length === 0,
|
||||||
|
error: {
|
||||||
|
issues: dependencyErrors
|
||||||
|
.map((message) => message)
|
||||||
|
.filter((message) => !!message),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
// If we have a value and dependencies are met, run contextual validation
|
||||||
|
const result = model.contextualValidation.validate({
|
||||||
|
value: varState.effectiveValue,
|
||||||
|
variables: allVariables,
|
||||||
|
mode,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!result.success) {
|
||||||
|
validation = {
|
||||||
|
success: false,
|
||||||
|
error: {
|
||||||
|
issues: result.error.issues
|
||||||
|
.map((issue) => issue.message)
|
||||||
|
.filter((message) => !!message),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (model.validate && varState.effectiveValue) {
|
||||||
|
// Only run regular validation if:
|
||||||
|
// 1. There's no contextual validation
|
||||||
|
// 2. There's a value to validate
|
||||||
|
const result = model.validate({
|
||||||
|
value: varState.effectiveValue,
|
||||||
|
variables: allVariables,
|
||||||
|
mode,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!result.success) {
|
||||||
|
validation = {
|
||||||
|
success: false,
|
||||||
|
error: {
|
||||||
|
issues: result.error.issues
|
||||||
|
.map((issue) => issue.message)
|
||||||
|
.filter((message) => !!message),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
varState.validation = validation;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Final pass: Validate missing variables that are marked as required
|
||||||
|
// or as having contextual validation
|
||||||
|
for (const model of envVariables) {
|
||||||
|
// If the variable exists in appState, use that
|
||||||
|
const existingVar = variableMap[model.name];
|
||||||
|
|
||||||
|
if (existingVar) {
|
||||||
|
// If the variable is already in the map, skip it
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (model.required || model.contextualValidation) {
|
||||||
|
if (model.contextualValidation) {
|
||||||
|
const allVariables = Object.values(variableMap).reduce(
|
||||||
|
(acc, variable) => {
|
||||||
|
return {
|
||||||
|
...acc,
|
||||||
|
[variable.key]: variable.effectiveValue,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
{} as Record<string, string>,
|
||||||
|
);
|
||||||
|
|
||||||
|
const errors =
|
||||||
|
model?.contextualValidation?.dependencies
|
||||||
|
.map((dep) => {
|
||||||
|
const dependencyValue = allVariables[dep.variable] ?? '';
|
||||||
|
const shouldValidate = dep.condition(
|
||||||
|
dependencyValue,
|
||||||
|
allVariables,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!shouldValidate) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const effectiveValue = allVariables[dep.variable] ?? '';
|
||||||
|
|
||||||
|
const validation = model.contextualValidation!.validate({
|
||||||
|
value: effectiveValue,
|
||||||
|
variables: allVariables,
|
||||||
|
mode,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (validation) {
|
||||||
|
return [dep.message];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
|
})
|
||||||
|
.flat() ?? ([] as string[]);
|
||||||
|
|
||||||
|
if (errors.length === 0) {
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
variableMap[model.name] = {
|
||||||
|
key: model.name,
|
||||||
|
effectiveValue: '',
|
||||||
|
effectiveSource: 'MISSING',
|
||||||
|
isVisible: true,
|
||||||
|
category: model.category,
|
||||||
|
isOverridden: false,
|
||||||
|
definitions: [],
|
||||||
|
validation: {
|
||||||
|
success: false,
|
||||||
|
error: {
|
||||||
|
issues: errors.map((error) => error),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If it doesn't exist but is required or has contextual validation, create an empty state
|
||||||
|
variableMap[model.name] = {
|
||||||
|
key: model.name,
|
||||||
|
effectiveValue: '',
|
||||||
|
effectiveSource: 'MISSING',
|
||||||
|
isVisible: true,
|
||||||
|
category: model.category,
|
||||||
|
isOverridden: false,
|
||||||
|
definitions: [],
|
||||||
|
validation: {
|
||||||
|
success: false,
|
||||||
|
error: {
|
||||||
|
issues: [
|
||||||
|
`This variable is required but missing from your environment files`,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
appName: envInfo.appName,
|
appName: envInfo.appName,
|
||||||
filePath: envInfo.filePath,
|
filePath: envInfo.filePath,
|
||||||
@@ -227,11 +459,6 @@ export async function getEnvState(
|
|||||||
return envInfos.map((info) => processEnvDefinitions(info, options.mode));
|
return envInfos.map((info) => processEnvDefinitions(info, options.mode));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Utility function to get list of env files for current mode
|
|
||||||
export function getEnvFilesForMode(mode: EnvMode): string[] {
|
|
||||||
return ENV_FILE_PRECEDENCE[mode];
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getVariable(key: string, mode: EnvMode) {
|
export async function getVariable(key: string, mode: EnvMode) {
|
||||||
// Get the processed environment state for all apps (you can limit to 'web' via options)
|
// Get the processed environment state for all apps (you can limit to 'web' via options)
|
||||||
const envStates = await getEnvState({ mode, apps: ['web'] });
|
const envStates = await getEnvState({ mode, apps: ['web'] });
|
||||||
|
|||||||
@@ -1,34 +1,51 @@
|
|||||||
import { EnvMode } from '@/app/variables/lib/types';
|
import { EnvMode } from '@/app/variables/lib/types';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
type DependencyRule = {
|
type ModelType =
|
||||||
variable: string;
|
| 'string'
|
||||||
condition: (value: string, variables: Record<string, string>) => boolean;
|
| 'longString'
|
||||||
message: string;
|
| 'number'
|
||||||
};
|
| 'boolean'
|
||||||
|
| 'enum'
|
||||||
|
| 'url'
|
||||||
|
| 'email';
|
||||||
|
|
||||||
type ContextualValidation = {
|
type Values = Array<string | null>;
|
||||||
dependencies: DependencyRule[];
|
|
||||||
validate: (props: {
|
|
||||||
value: string;
|
|
||||||
variables: Record<string, string>;
|
|
||||||
mode: EnvMode;
|
|
||||||
}) => z.SafeParseReturnType<unknown, unknown>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type EnvVariableModel = {
|
export type EnvVariableModel = {
|
||||||
name: string;
|
name: string;
|
||||||
description: string;
|
description: string;
|
||||||
|
hint?: string;
|
||||||
secret?: boolean;
|
secret?: boolean;
|
||||||
required?: boolean;
|
type?: ModelType;
|
||||||
|
values?: Values;
|
||||||
category: string;
|
category: string;
|
||||||
test?: (value: string) => Promise<boolean>;
|
required?: boolean;
|
||||||
validate?: (props: {
|
validate?: ({
|
||||||
|
value,
|
||||||
|
variables,
|
||||||
|
mode,
|
||||||
|
}: {
|
||||||
value: string;
|
value: string;
|
||||||
variables: Record<string, string>;
|
variables: Record<string, string>;
|
||||||
mode: EnvMode;
|
mode: EnvMode;
|
||||||
}) => z.SafeParseReturnType<unknown, unknown>;
|
}) => z.SafeParseReturnType<unknown, unknown>;
|
||||||
contextualValidation?: ContextualValidation;
|
contextualValidation?: {
|
||||||
|
dependencies: Array<{
|
||||||
|
variable: string;
|
||||||
|
condition: (value: string, variables: Record<string, string>) => boolean;
|
||||||
|
message: string;
|
||||||
|
}>;
|
||||||
|
validate: ({
|
||||||
|
value,
|
||||||
|
variables,
|
||||||
|
mode,
|
||||||
|
}: {
|
||||||
|
value: string;
|
||||||
|
variables: Record<string, string>;
|
||||||
|
mode: EnvMode;
|
||||||
|
}) => z.SafeParseReturnType<unknown, unknown>;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const envVariables: EnvVariableModel[] = [
|
export const envVariables: EnvVariableModel[] = [
|
||||||
@@ -38,6 +55,8 @@ export const envVariables: EnvVariableModel[] = [
|
|||||||
'The URL of your site, used for generating absolute URLs. Must include the protocol.',
|
'The URL of your site, used for generating absolute URLs. Must include the protocol.',
|
||||||
category: 'Site Configuration',
|
category: 'Site Configuration',
|
||||||
required: true,
|
required: true,
|
||||||
|
type: 'url',
|
||||||
|
hint: `Ex. https://example.com`,
|
||||||
validate: ({ value, mode }) => {
|
validate: ({ value, mode }) => {
|
||||||
if (mode === 'development') {
|
if (mode === 'development') {
|
||||||
return z
|
return z
|
||||||
@@ -65,7 +84,9 @@ export const envVariables: EnvVariableModel[] = [
|
|||||||
description:
|
description:
|
||||||
"Your product's name, used consistently across the application interface.",
|
"Your product's name, used consistently across the application interface.",
|
||||||
category: 'Site Configuration',
|
category: 'Site Configuration',
|
||||||
|
hint: `Ex. "My Product"`,
|
||||||
required: true,
|
required: true,
|
||||||
|
type: 'string',
|
||||||
validate: ({ value }) => {
|
validate: ({ value }) => {
|
||||||
return z
|
return z
|
||||||
.string()
|
.string()
|
||||||
@@ -82,6 +103,8 @@ export const envVariables: EnvVariableModel[] = [
|
|||||||
"The site's title tag content, crucial for SEO and browser display.",
|
"The site's title tag content, crucial for SEO and browser display.",
|
||||||
category: 'Site Configuration',
|
category: 'Site Configuration',
|
||||||
required: true,
|
required: true,
|
||||||
|
hint: `Ex. "My Product, the best product ever"`,
|
||||||
|
type: 'string',
|
||||||
validate: ({ value }) => {
|
validate: ({ value }) => {
|
||||||
return z
|
return z
|
||||||
.string()
|
.string()
|
||||||
@@ -94,6 +117,7 @@ export const envVariables: EnvVariableModel[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'NEXT_PUBLIC_SITE_DESCRIPTION',
|
name: 'NEXT_PUBLIC_SITE_DESCRIPTION',
|
||||||
|
type: 'longString',
|
||||||
description:
|
description:
|
||||||
"Your site's meta description, important for SEO optimization.",
|
"Your site's meta description, important for SEO optimization.",
|
||||||
category: 'Site Configuration',
|
category: 'Site Configuration',
|
||||||
@@ -111,8 +135,10 @@ export const envVariables: EnvVariableModel[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'NEXT_PUBLIC_DEFAULT_LOCALE',
|
name: 'NEXT_PUBLIC_DEFAULT_LOCALE',
|
||||||
|
type: 'string',
|
||||||
description: 'Sets the default language for your application.',
|
description: 'Sets the default language for your application.',
|
||||||
category: 'Localization',
|
category: 'Localization',
|
||||||
|
hint: `Ex. "en"`,
|
||||||
validate: ({ value }) => {
|
validate: ({ value }) => {
|
||||||
return z
|
return z
|
||||||
.string()
|
.string()
|
||||||
@@ -128,6 +154,7 @@ export const envVariables: EnvVariableModel[] = [
|
|||||||
name: 'NEXT_PUBLIC_AUTH_PASSWORD',
|
name: 'NEXT_PUBLIC_AUTH_PASSWORD',
|
||||||
description: 'Enables or disables password-based authentication.',
|
description: 'Enables or disables password-based authentication.',
|
||||||
category: 'Authentication',
|
category: 'Authentication',
|
||||||
|
type: 'boolean',
|
||||||
validate: ({ value }) => {
|
validate: ({ value }) => {
|
||||||
return z.coerce.boolean().optional().safeParse(value);
|
return z.coerce.boolean().optional().safeParse(value);
|
||||||
},
|
},
|
||||||
@@ -136,6 +163,7 @@ export const envVariables: EnvVariableModel[] = [
|
|||||||
name: 'NEXT_PUBLIC_AUTH_MAGIC_LINK',
|
name: 'NEXT_PUBLIC_AUTH_MAGIC_LINK',
|
||||||
description: 'Enables or disables magic link authentication.',
|
description: 'Enables or disables magic link authentication.',
|
||||||
category: 'Authentication',
|
category: 'Authentication',
|
||||||
|
type: 'boolean',
|
||||||
validate: ({ value }) => {
|
validate: ({ value }) => {
|
||||||
return z.coerce.boolean().optional().safeParse(value);
|
return z.coerce.boolean().optional().safeParse(value);
|
||||||
},
|
},
|
||||||
@@ -144,6 +172,7 @@ export const envVariables: EnvVariableModel[] = [
|
|||||||
name: 'NEXT_PUBLIC_CAPTCHA_SITE_KEY',
|
name: 'NEXT_PUBLIC_CAPTCHA_SITE_KEY',
|
||||||
description: 'Your Cloudflare Captcha site key for form protection.',
|
description: 'Your Cloudflare Captcha site key for form protection.',
|
||||||
category: 'Security',
|
category: 'Security',
|
||||||
|
type: 'string',
|
||||||
validate: ({ value }) => {
|
validate: ({ value }) => {
|
||||||
return z.string().optional().safeParse(value);
|
return z.string().optional().safeParse(value);
|
||||||
},
|
},
|
||||||
@@ -154,6 +183,7 @@ export const envVariables: EnvVariableModel[] = [
|
|||||||
'Your Cloudflare Captcha secret token for backend verification.',
|
'Your Cloudflare Captcha secret token for backend verification.',
|
||||||
category: 'Security',
|
category: 'Security',
|
||||||
secret: true,
|
secret: true,
|
||||||
|
type: 'string',
|
||||||
contextualValidation: {
|
contextualValidation: {
|
||||||
dependencies: [
|
dependencies: [
|
||||||
{
|
{
|
||||||
@@ -191,6 +221,8 @@ export const envVariables: EnvVariableModel[] = [
|
|||||||
description:
|
description:
|
||||||
'Controls user navigation layout. Options: sidebar, header, or custom.',
|
'Controls user navigation layout. Options: sidebar, header, or custom.',
|
||||||
category: 'Navigation',
|
category: 'Navigation',
|
||||||
|
type: 'enum',
|
||||||
|
values: ['sidebar', 'header', 'custom'],
|
||||||
validate: ({ value }) => {
|
validate: ({ value }) => {
|
||||||
return z
|
return z
|
||||||
.enum(['sidebar', 'header', 'custom'])
|
.enum(['sidebar', 'header', 'custom'])
|
||||||
@@ -202,6 +234,7 @@ export const envVariables: EnvVariableModel[] = [
|
|||||||
name: 'NEXT_PUBLIC_HOME_SIDEBAR_COLLAPSED',
|
name: 'NEXT_PUBLIC_HOME_SIDEBAR_COLLAPSED',
|
||||||
description: 'Sets the default state of the home sidebar.',
|
description: 'Sets the default state of the home sidebar.',
|
||||||
category: 'Navigation',
|
category: 'Navigation',
|
||||||
|
type: 'boolean',
|
||||||
validate: ({ value }) => {
|
validate: ({ value }) => {
|
||||||
return z.coerce.boolean().optional().safeParse(value);
|
return z.coerce.boolean().optional().safeParse(value);
|
||||||
},
|
},
|
||||||
@@ -211,6 +244,8 @@ export const envVariables: EnvVariableModel[] = [
|
|||||||
description:
|
description:
|
||||||
'Controls team navigation layout. Options: sidebar, header, or custom.',
|
'Controls team navigation layout. Options: sidebar, header, or custom.',
|
||||||
category: 'Navigation',
|
category: 'Navigation',
|
||||||
|
type: 'enum',
|
||||||
|
values: ['sidebar', 'header', 'custom'],
|
||||||
validate: ({ value }) => {
|
validate: ({ value }) => {
|
||||||
return z
|
return z
|
||||||
.enum(['sidebar', 'header', 'custom'])
|
.enum(['sidebar', 'header', 'custom'])
|
||||||
@@ -222,6 +257,7 @@ export const envVariables: EnvVariableModel[] = [
|
|||||||
name: 'NEXT_PUBLIC_TEAM_SIDEBAR_COLLAPSED',
|
name: 'NEXT_PUBLIC_TEAM_SIDEBAR_COLLAPSED',
|
||||||
description: 'Sets the default state of the team sidebar.',
|
description: 'Sets the default state of the team sidebar.',
|
||||||
category: 'Navigation',
|
category: 'Navigation',
|
||||||
|
type: 'boolean',
|
||||||
validate: ({ value }) => {
|
validate: ({ value }) => {
|
||||||
return z.coerce.boolean().optional().safeParse(value);
|
return z.coerce.boolean().optional().safeParse(value);
|
||||||
},
|
},
|
||||||
@@ -231,6 +267,8 @@ export const envVariables: EnvVariableModel[] = [
|
|||||||
description:
|
description:
|
||||||
'Defines sidebar collapse behavior. Options: offscreen, icon, or none.',
|
'Defines sidebar collapse behavior. Options: offscreen, icon, or none.',
|
||||||
category: 'Navigation',
|
category: 'Navigation',
|
||||||
|
type: 'enum',
|
||||||
|
values: ['offscreen', 'icon', 'none'],
|
||||||
validate: ({ value }) => {
|
validate: ({ value }) => {
|
||||||
return z.enum(['offscreen', 'icon', 'none']).optional().safeParse(value);
|
return z.enum(['offscreen', 'icon', 'none']).optional().safeParse(value);
|
||||||
},
|
},
|
||||||
@@ -240,6 +278,8 @@ export const envVariables: EnvVariableModel[] = [
|
|||||||
description:
|
description:
|
||||||
'Controls the default theme appearance. Options: light, dark, or system.',
|
'Controls the default theme appearance. Options: light, dark, or system.',
|
||||||
category: 'Theme',
|
category: 'Theme',
|
||||||
|
type: 'enum',
|
||||||
|
values: ['light', 'dark', 'system'],
|
||||||
validate: ({ value }) => {
|
validate: ({ value }) => {
|
||||||
return z.enum(['light', 'dark', 'system']).optional().safeParse(value);
|
return z.enum(['light', 'dark', 'system']).optional().safeParse(value);
|
||||||
},
|
},
|
||||||
@@ -248,6 +288,7 @@ export const envVariables: EnvVariableModel[] = [
|
|||||||
name: 'NEXT_PUBLIC_ENABLE_THEME_TOGGLE',
|
name: 'NEXT_PUBLIC_ENABLE_THEME_TOGGLE',
|
||||||
description: 'Controls visibility of the theme toggle feature.',
|
description: 'Controls visibility of the theme toggle feature.',
|
||||||
category: 'Theme',
|
category: 'Theme',
|
||||||
|
type: 'boolean',
|
||||||
validate: ({ value }) => {
|
validate: ({ value }) => {
|
||||||
return z.coerce.boolean().optional().safeParse(value);
|
return z.coerce.boolean().optional().safeParse(value);
|
||||||
},
|
},
|
||||||
@@ -256,6 +297,7 @@ export const envVariables: EnvVariableModel[] = [
|
|||||||
name: 'NEXT_PUBLIC_ENABLE_SIDEBAR_TRIGGER',
|
name: 'NEXT_PUBLIC_ENABLE_SIDEBAR_TRIGGER',
|
||||||
description: 'Controls visibility of the sidebar trigger feature.',
|
description: 'Controls visibility of the sidebar trigger feature.',
|
||||||
category: 'Navigation',
|
category: 'Navigation',
|
||||||
|
type: 'boolean',
|
||||||
validate: ({ value }) => {
|
validate: ({ value }) => {
|
||||||
return z.coerce.boolean().optional().safeParse(value);
|
return z.coerce.boolean().optional().safeParse(value);
|
||||||
},
|
},
|
||||||
@@ -264,6 +306,7 @@ export const envVariables: EnvVariableModel[] = [
|
|||||||
name: 'NEXT_PUBLIC_ENABLE_PERSONAL_ACCOUNT_DELETION',
|
name: 'NEXT_PUBLIC_ENABLE_PERSONAL_ACCOUNT_DELETION',
|
||||||
description: 'Allows users to delete their personal accounts.',
|
description: 'Allows users to delete their personal accounts.',
|
||||||
category: 'Features',
|
category: 'Features',
|
||||||
|
type: 'boolean',
|
||||||
validate: ({ value }) => {
|
validate: ({ value }) => {
|
||||||
return z.coerce.boolean().optional().safeParse(value);
|
return z.coerce.boolean().optional().safeParse(value);
|
||||||
},
|
},
|
||||||
@@ -272,6 +315,7 @@ export const envVariables: EnvVariableModel[] = [
|
|||||||
name: 'NEXT_PUBLIC_ENABLE_PERSONAL_ACCOUNT_BILLING',
|
name: 'NEXT_PUBLIC_ENABLE_PERSONAL_ACCOUNT_BILLING',
|
||||||
description: 'Enables billing features for personal accounts.',
|
description: 'Enables billing features for personal accounts.',
|
||||||
category: 'Features',
|
category: 'Features',
|
||||||
|
type: 'boolean',
|
||||||
validate: ({ value }) => {
|
validate: ({ value }) => {
|
||||||
return z.coerce.boolean().optional().safeParse(value);
|
return z.coerce.boolean().optional().safeParse(value);
|
||||||
},
|
},
|
||||||
@@ -280,6 +324,7 @@ export const envVariables: EnvVariableModel[] = [
|
|||||||
name: 'NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS',
|
name: 'NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS',
|
||||||
description: 'Master switch for team account functionality.',
|
description: 'Master switch for team account functionality.',
|
||||||
category: 'Features',
|
category: 'Features',
|
||||||
|
type: 'boolean',
|
||||||
validate: ({ value }) => {
|
validate: ({ value }) => {
|
||||||
return z.coerce.boolean().optional().safeParse(value);
|
return z.coerce.boolean().optional().safeParse(value);
|
||||||
},
|
},
|
||||||
@@ -288,6 +333,7 @@ export const envVariables: EnvVariableModel[] = [
|
|||||||
name: 'NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS_CREATION',
|
name: 'NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS_CREATION',
|
||||||
description: 'Controls ability to create new team accounts.',
|
description: 'Controls ability to create new team accounts.',
|
||||||
category: 'Features',
|
category: 'Features',
|
||||||
|
type: 'boolean',
|
||||||
validate: ({ value }) => {
|
validate: ({ value }) => {
|
||||||
return z.coerce.boolean().optional().safeParse(value);
|
return z.coerce.boolean().optional().safeParse(value);
|
||||||
},
|
},
|
||||||
@@ -296,6 +342,7 @@ export const envVariables: EnvVariableModel[] = [
|
|||||||
name: 'NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS_DELETION',
|
name: 'NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS_DELETION',
|
||||||
description: 'Allows team account deletion.',
|
description: 'Allows team account deletion.',
|
||||||
category: 'Features',
|
category: 'Features',
|
||||||
|
type: 'boolean',
|
||||||
validate: ({ value }) => {
|
validate: ({ value }) => {
|
||||||
return z.coerce.boolean().optional().safeParse(value);
|
return z.coerce.boolean().optional().safeParse(value);
|
||||||
},
|
},
|
||||||
@@ -304,6 +351,7 @@ export const envVariables: EnvVariableModel[] = [
|
|||||||
name: 'NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS_BILLING',
|
name: 'NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS_BILLING',
|
||||||
description: 'Enables billing features for team accounts.',
|
description: 'Enables billing features for team accounts.',
|
||||||
category: 'Features',
|
category: 'Features',
|
||||||
|
type: 'boolean',
|
||||||
validate: ({ value }) => {
|
validate: ({ value }) => {
|
||||||
return z.coerce.boolean().optional().safeParse(value);
|
return z.coerce.boolean().optional().safeParse(value);
|
||||||
},
|
},
|
||||||
@@ -312,6 +360,7 @@ export const envVariables: EnvVariableModel[] = [
|
|||||||
name: 'NEXT_PUBLIC_ENABLE_NOTIFICATIONS',
|
name: 'NEXT_PUBLIC_ENABLE_NOTIFICATIONS',
|
||||||
description: 'Controls the notification system.',
|
description: 'Controls the notification system.',
|
||||||
category: 'Notifications',
|
category: 'Notifications',
|
||||||
|
type: 'boolean',
|
||||||
validate: ({ value }) => {
|
validate: ({ value }) => {
|
||||||
return z.coerce.boolean().optional().safeParse(value);
|
return z.coerce.boolean().optional().safeParse(value);
|
||||||
},
|
},
|
||||||
@@ -320,6 +369,7 @@ export const envVariables: EnvVariableModel[] = [
|
|||||||
name: 'NEXT_PUBLIC_REALTIME_NOTIFICATIONS',
|
name: 'NEXT_PUBLIC_REALTIME_NOTIFICATIONS',
|
||||||
description: 'Enables real-time notifications using Supabase Realtime.',
|
description: 'Enables real-time notifications using Supabase Realtime.',
|
||||||
category: 'Notifications',
|
category: 'Notifications',
|
||||||
|
type: 'boolean',
|
||||||
validate: ({ value }) => {
|
validate: ({ value }) => {
|
||||||
return z.coerce.boolean().optional().safeParse(value);
|
return z.coerce.boolean().optional().safeParse(value);
|
||||||
},
|
},
|
||||||
@@ -328,7 +378,9 @@ export const envVariables: EnvVariableModel[] = [
|
|||||||
name: 'NEXT_PUBLIC_SUPABASE_URL',
|
name: 'NEXT_PUBLIC_SUPABASE_URL',
|
||||||
description: 'Your Supabase project URL.',
|
description: 'Your Supabase project URL.',
|
||||||
category: 'Supabase',
|
category: 'Supabase',
|
||||||
|
hint: `Ex. https://your-project.supabase.co`,
|
||||||
required: true,
|
required: true,
|
||||||
|
type: 'url',
|
||||||
validate: ({ value, mode }) => {
|
validate: ({ value, mode }) => {
|
||||||
if (mode === 'development') {
|
if (mode === 'development') {
|
||||||
return z
|
return z
|
||||||
@@ -356,6 +408,7 @@ export const envVariables: EnvVariableModel[] = [
|
|||||||
description: 'Your Supabase anonymous API key.',
|
description: 'Your Supabase anonymous API key.',
|
||||||
category: 'Supabase',
|
category: 'Supabase',
|
||||||
required: true,
|
required: true,
|
||||||
|
type: 'string',
|
||||||
validate: ({ value }) => {
|
validate: ({ value }) => {
|
||||||
return z
|
return z
|
||||||
.string()
|
.string()
|
||||||
@@ -372,6 +425,7 @@ export const envVariables: EnvVariableModel[] = [
|
|||||||
category: 'Supabase',
|
category: 'Supabase',
|
||||||
secret: true,
|
secret: true,
|
||||||
required: true,
|
required: true,
|
||||||
|
type: 'string',
|
||||||
validate: ({ value, variables }) => {
|
validate: ({ value, variables }) => {
|
||||||
return z
|
return z
|
||||||
.string()
|
.string()
|
||||||
@@ -396,6 +450,7 @@ export const envVariables: EnvVariableModel[] = [
|
|||||||
category: 'Supabase',
|
category: 'Supabase',
|
||||||
secret: true,
|
secret: true,
|
||||||
required: true,
|
required: true,
|
||||||
|
type: 'string',
|
||||||
validate: ({ value }) => {
|
validate: ({ value }) => {
|
||||||
return z
|
return z
|
||||||
.string()
|
.string()
|
||||||
@@ -413,6 +468,8 @@ export const envVariables: EnvVariableModel[] = [
|
|||||||
'Your chosen billing provider. Options: stripe or lemon-squeezy.',
|
'Your chosen billing provider. Options: stripe or lemon-squeezy.',
|
||||||
category: 'Billing',
|
category: 'Billing',
|
||||||
required: true,
|
required: true,
|
||||||
|
type: 'enum',
|
||||||
|
values: ['stripe', 'lemon-squeezy'],
|
||||||
validate: ({ value }) => {
|
validate: ({ value }) => {
|
||||||
return z.enum(['stripe', 'lemon-squeezy']).optional().safeParse(value);
|
return z.enum(['stripe', 'lemon-squeezy']).optional().safeParse(value);
|
||||||
},
|
},
|
||||||
@@ -420,7 +477,9 @@ export const envVariables: EnvVariableModel[] = [
|
|||||||
{
|
{
|
||||||
name: 'NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY',
|
name: 'NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY',
|
||||||
description: 'Your Stripe publishable key.',
|
description: 'Your Stripe publishable key.',
|
||||||
|
hint: `Ex. pk_test_123456789012345678901234`,
|
||||||
category: 'Billing',
|
category: 'Billing',
|
||||||
|
type: 'string',
|
||||||
contextualValidation: {
|
contextualValidation: {
|
||||||
dependencies: [
|
dependencies: [
|
||||||
{
|
{
|
||||||
@@ -463,7 +522,9 @@ export const envVariables: EnvVariableModel[] = [
|
|||||||
name: 'STRIPE_SECRET_KEY',
|
name: 'STRIPE_SECRET_KEY',
|
||||||
description: 'Your Stripe secret key.',
|
description: 'Your Stripe secret key.',
|
||||||
category: 'Billing',
|
category: 'Billing',
|
||||||
|
hint: `Ex. sk_test_123456789012345678901234`,
|
||||||
secret: true,
|
secret: true,
|
||||||
|
type: 'string',
|
||||||
contextualValidation: {
|
contextualValidation: {
|
||||||
dependencies: [
|
dependencies: [
|
||||||
{
|
{
|
||||||
@@ -500,7 +561,9 @@ export const envVariables: EnvVariableModel[] = [
|
|||||||
name: 'STRIPE_WEBHOOK_SECRET',
|
name: 'STRIPE_WEBHOOK_SECRET',
|
||||||
description: 'Your Stripe webhook secret.',
|
description: 'Your Stripe webhook secret.',
|
||||||
category: 'Billing',
|
category: 'Billing',
|
||||||
|
hint: `Ex. whsec_123456789012345678901234`,
|
||||||
secret: true,
|
secret: true,
|
||||||
|
type: 'string',
|
||||||
contextualValidation: {
|
contextualValidation: {
|
||||||
dependencies: [
|
dependencies: [
|
||||||
{
|
{
|
||||||
@@ -544,6 +607,7 @@ export const envVariables: EnvVariableModel[] = [
|
|||||||
description: 'Your Lemon Squeezy secret key.',
|
description: 'Your Lemon Squeezy secret key.',
|
||||||
category: 'Billing',
|
category: 'Billing',
|
||||||
secret: true,
|
secret: true,
|
||||||
|
type: 'string',
|
||||||
contextualValidation: {
|
contextualValidation: {
|
||||||
dependencies: [
|
dependencies: [
|
||||||
{
|
{
|
||||||
@@ -578,6 +642,7 @@ export const envVariables: EnvVariableModel[] = [
|
|||||||
name: 'LEMON_SQUEEZY_STORE_ID',
|
name: 'LEMON_SQUEEZY_STORE_ID',
|
||||||
description: 'Your Lemon Squeezy store ID.',
|
description: 'Your Lemon Squeezy store ID.',
|
||||||
category: 'Billing',
|
category: 'Billing',
|
||||||
|
type: 'string',
|
||||||
contextualValidation: {
|
contextualValidation: {
|
||||||
dependencies: [
|
dependencies: [
|
||||||
{
|
{
|
||||||
@@ -613,6 +678,7 @@ export const envVariables: EnvVariableModel[] = [
|
|||||||
description: 'Your Lemon Squeezy signing secret.',
|
description: 'Your Lemon Squeezy signing secret.',
|
||||||
category: 'Billing',
|
category: 'Billing',
|
||||||
secret: true,
|
secret: true,
|
||||||
|
type: 'string',
|
||||||
contextualValidation: {
|
contextualValidation: {
|
||||||
dependencies: [
|
dependencies: [
|
||||||
{
|
{
|
||||||
@@ -648,6 +714,8 @@ export const envVariables: EnvVariableModel[] = [
|
|||||||
description: 'Your email service provider. Options: nodemailer or resend.',
|
description: 'Your email service provider. Options: nodemailer or resend.',
|
||||||
category: 'Email',
|
category: 'Email',
|
||||||
required: true,
|
required: true,
|
||||||
|
type: 'enum',
|
||||||
|
values: ['nodemailer', 'resend'],
|
||||||
validate: ({ value }) => {
|
validate: ({ value }) => {
|
||||||
return z.enum(['nodemailer', 'resend']).safeParse(value);
|
return z.enum(['nodemailer', 'resend']).safeParse(value);
|
||||||
},
|
},
|
||||||
@@ -656,7 +724,9 @@ export const envVariables: EnvVariableModel[] = [
|
|||||||
name: 'EMAIL_SENDER',
|
name: 'EMAIL_SENDER',
|
||||||
description: 'Default sender email address.',
|
description: 'Default sender email address.',
|
||||||
category: 'Email',
|
category: 'Email',
|
||||||
|
hint: `Ex. "Makerkit <admin@makerkit.dev>"`,
|
||||||
required: true,
|
required: true,
|
||||||
|
type: 'string',
|
||||||
validate: ({ value }) => {
|
validate: ({ value }) => {
|
||||||
return z
|
return z
|
||||||
.string()
|
.string()
|
||||||
@@ -668,7 +738,9 @@ export const envVariables: EnvVariableModel[] = [
|
|||||||
name: 'CONTACT_EMAIL',
|
name: 'CONTACT_EMAIL',
|
||||||
description: 'Email address for contact form submissions.',
|
description: 'Email address for contact form submissions.',
|
||||||
category: 'Email',
|
category: 'Email',
|
||||||
|
hint: `Ex. "Makerkit <admin@makerkit.dev>"`,
|
||||||
required: true,
|
required: true,
|
||||||
|
type: 'email',
|
||||||
validate: ({ value }) => {
|
validate: ({ value }) => {
|
||||||
return z
|
return z
|
||||||
.string()
|
.string()
|
||||||
@@ -682,6 +754,7 @@ export const envVariables: EnvVariableModel[] = [
|
|||||||
description: 'Your Resend API key.',
|
description: 'Your Resend API key.',
|
||||||
category: 'Email',
|
category: 'Email',
|
||||||
secret: true,
|
secret: true,
|
||||||
|
type: 'string',
|
||||||
contextualValidation: {
|
contextualValidation: {
|
||||||
dependencies: [
|
dependencies: [
|
||||||
{
|
{
|
||||||
@@ -707,6 +780,8 @@ export const envVariables: EnvVariableModel[] = [
|
|||||||
name: 'EMAIL_HOST',
|
name: 'EMAIL_HOST',
|
||||||
description: 'SMTP host for Nodemailer configuration.',
|
description: 'SMTP host for Nodemailer configuration.',
|
||||||
category: 'Email',
|
category: 'Email',
|
||||||
|
type: 'string',
|
||||||
|
hint: `Ex. "smtp.example.com"`,
|
||||||
contextualValidation: {
|
contextualValidation: {
|
||||||
dependencies: [
|
dependencies: [
|
||||||
{
|
{
|
||||||
@@ -731,6 +806,8 @@ export const envVariables: EnvVariableModel[] = [
|
|||||||
name: 'EMAIL_PORT',
|
name: 'EMAIL_PORT',
|
||||||
description: 'SMTP port for Nodemailer configuration.',
|
description: 'SMTP port for Nodemailer configuration.',
|
||||||
category: 'Email',
|
category: 'Email',
|
||||||
|
type: 'number',
|
||||||
|
hint: `Ex. 587 or 465`,
|
||||||
contextualValidation: {
|
contextualValidation: {
|
||||||
dependencies: [
|
dependencies: [
|
||||||
{
|
{
|
||||||
@@ -756,6 +833,7 @@ export const envVariables: EnvVariableModel[] = [
|
|||||||
name: 'EMAIL_USER',
|
name: 'EMAIL_USER',
|
||||||
description: 'SMTP user for Nodemailer configuration.',
|
description: 'SMTP user for Nodemailer configuration.',
|
||||||
category: 'Email',
|
category: 'Email',
|
||||||
|
type: 'string',
|
||||||
contextualValidation: {
|
contextualValidation: {
|
||||||
dependencies: [
|
dependencies: [
|
||||||
{
|
{
|
||||||
@@ -782,6 +860,7 @@ export const envVariables: EnvVariableModel[] = [
|
|||||||
description: 'SMTP password for Nodemailer configuration.',
|
description: 'SMTP password for Nodemailer configuration.',
|
||||||
category: 'Email',
|
category: 'Email',
|
||||||
secret: true,
|
secret: true,
|
||||||
|
type: 'string',
|
||||||
contextualValidation: {
|
contextualValidation: {
|
||||||
dependencies: [
|
dependencies: [
|
||||||
{
|
{
|
||||||
@@ -804,8 +883,9 @@ export const envVariables: EnvVariableModel[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'EMAIL_TLS',
|
name: 'EMAIL_TLS',
|
||||||
description: 'Whether to use TLS for SMTP connection.',
|
description: 'Whether to use TLS for SMTP connection. Please check this in your SMTP provider settings.',
|
||||||
category: 'Email',
|
category: 'Email',
|
||||||
|
type: 'boolean',
|
||||||
contextualValidation: {
|
contextualValidation: {
|
||||||
dependencies: [
|
dependencies: [
|
||||||
{
|
{
|
||||||
@@ -830,6 +910,8 @@ export const envVariables: EnvVariableModel[] = [
|
|||||||
name: 'CMS_CLIENT',
|
name: 'CMS_CLIENT',
|
||||||
description: 'Your chosen CMS system. Options: wordpress or keystatic.',
|
description: 'Your chosen CMS system. Options: wordpress or keystatic.',
|
||||||
category: 'CMS',
|
category: 'CMS',
|
||||||
|
type: 'enum',
|
||||||
|
values: ['wordpress', 'keystatic'],
|
||||||
validate: ({ value }) => {
|
validate: ({ value }) => {
|
||||||
return z.enum(['wordpress', 'keystatic']).optional().safeParse(value);
|
return z.enum(['wordpress', 'keystatic']).optional().safeParse(value);
|
||||||
},
|
},
|
||||||
@@ -838,6 +920,8 @@ export const envVariables: EnvVariableModel[] = [
|
|||||||
name: 'NEXT_PUBLIC_KEYSTATIC_STORAGE_KIND',
|
name: 'NEXT_PUBLIC_KEYSTATIC_STORAGE_KIND',
|
||||||
description: 'Your Keystatic storage kind. Options: local, cloud, github.',
|
description: 'Your Keystatic storage kind. Options: local, cloud, github.',
|
||||||
category: 'CMS',
|
category: 'CMS',
|
||||||
|
type: 'enum',
|
||||||
|
values: ['local', 'cloud', 'github'],
|
||||||
contextualValidation: {
|
contextualValidation: {
|
||||||
dependencies: [
|
dependencies: [
|
||||||
{
|
{
|
||||||
@@ -862,6 +946,7 @@ export const envVariables: EnvVariableModel[] = [
|
|||||||
name: 'NEXT_PUBLIC_KEYSTATIC_STORAGE_REPO',
|
name: 'NEXT_PUBLIC_KEYSTATIC_STORAGE_REPO',
|
||||||
description: 'Your Keystatic storage repo.',
|
description: 'Your Keystatic storage repo.',
|
||||||
category: 'CMS',
|
category: 'CMS',
|
||||||
|
type: 'string',
|
||||||
contextualValidation: {
|
contextualValidation: {
|
||||||
dependencies: [
|
dependencies: [
|
||||||
{
|
{
|
||||||
@@ -889,6 +974,7 @@ export const envVariables: EnvVariableModel[] = [
|
|||||||
description: 'Your Keystatic GitHub token.',
|
description: 'Your Keystatic GitHub token.',
|
||||||
category: 'CMS',
|
category: 'CMS',
|
||||||
secret: true,
|
secret: true,
|
||||||
|
type: 'string',
|
||||||
contextualValidation: {
|
contextualValidation: {
|
||||||
dependencies: [
|
dependencies: [
|
||||||
{
|
{
|
||||||
@@ -915,6 +1001,7 @@ export const envVariables: EnvVariableModel[] = [
|
|||||||
name: 'KEYSTATIC_PATH_PREFIX',
|
name: 'KEYSTATIC_PATH_PREFIX',
|
||||||
description: 'Your Keystatic path prefix.',
|
description: 'Your Keystatic path prefix.',
|
||||||
category: 'CMS',
|
category: 'CMS',
|
||||||
|
type: 'string',
|
||||||
contextualValidation: {
|
contextualValidation: {
|
||||||
dependencies: [
|
dependencies: [
|
||||||
{
|
{
|
||||||
@@ -933,6 +1020,7 @@ export const envVariables: EnvVariableModel[] = [
|
|||||||
name: 'NEXT_PUBLIC_KEYSTATIC_CONTENT_PATH',
|
name: 'NEXT_PUBLIC_KEYSTATIC_CONTENT_PATH',
|
||||||
description: 'Your Keystatic content path.',
|
description: 'Your Keystatic content path.',
|
||||||
category: 'CMS',
|
category: 'CMS',
|
||||||
|
type: 'string',
|
||||||
contextualValidation: {
|
contextualValidation: {
|
||||||
dependencies: [
|
dependencies: [
|
||||||
{
|
{
|
||||||
@@ -958,6 +1046,7 @@ export const envVariables: EnvVariableModel[] = [
|
|||||||
name: 'WORDPRESS_API_URL',
|
name: 'WORDPRESS_API_URL',
|
||||||
description: 'WordPress API URL when using WordPress as CMS.',
|
description: 'WordPress API URL when using WordPress as CMS.',
|
||||||
category: 'CMS',
|
category: 'CMS',
|
||||||
|
type: 'string',
|
||||||
contextualValidation: {
|
contextualValidation: {
|
||||||
dependencies: [
|
dependencies: [
|
||||||
{
|
{
|
||||||
@@ -981,6 +1070,7 @@ export const envVariables: EnvVariableModel[] = [
|
|||||||
name: 'NEXT_PUBLIC_LOCALES_PATH',
|
name: 'NEXT_PUBLIC_LOCALES_PATH',
|
||||||
description: 'The path to your locales folder.',
|
description: 'The path to your locales folder.',
|
||||||
category: 'Localization',
|
category: 'Localization',
|
||||||
|
type: 'string',
|
||||||
validate: ({ value }) => {
|
validate: ({ value }) => {
|
||||||
return z
|
return z
|
||||||
.string()
|
.string()
|
||||||
@@ -996,6 +1086,8 @@ export const envVariables: EnvVariableModel[] = [
|
|||||||
name: 'NEXT_PUBLIC_LANGUAGE_PRIORITY',
|
name: 'NEXT_PUBLIC_LANGUAGE_PRIORITY',
|
||||||
description: 'The priority setting as to how infer the language.',
|
description: 'The priority setting as to how infer the language.',
|
||||||
category: 'Localization',
|
category: 'Localization',
|
||||||
|
type: 'enum',
|
||||||
|
values: ['user', 'application'],
|
||||||
validate: ({ value }) => {
|
validate: ({ value }) => {
|
||||||
return z.enum(['user', 'application']).optional().safeParse(value);
|
return z.enum(['user', 'application']).optional().safeParse(value);
|
||||||
},
|
},
|
||||||
@@ -1005,6 +1097,7 @@ export const envVariables: EnvVariableModel[] = [
|
|||||||
description:
|
description:
|
||||||
'Enables the version updater to poll the latest version and notify the user.',
|
'Enables the version updater to poll the latest version and notify the user.',
|
||||||
category: 'Features',
|
category: 'Features',
|
||||||
|
type: 'boolean',
|
||||||
validate: ({ value }) => {
|
validate: ({ value }) => {
|
||||||
return z.coerce.boolean().optional().safeParse(value);
|
return z.coerce.boolean().optional().safeParse(value);
|
||||||
},
|
},
|
||||||
@@ -1013,6 +1106,7 @@ export const envVariables: EnvVariableModel[] = [
|
|||||||
name: 'NEXT_PUBLIC_VERSION_UPDATER_REFETCH_INTERVAL_SECONDS',
|
name: 'NEXT_PUBLIC_VERSION_UPDATER_REFETCH_INTERVAL_SECONDS',
|
||||||
description: 'The interval in seconds to check for updates.',
|
description: 'The interval in seconds to check for updates.',
|
||||||
category: 'Features',
|
category: 'Features',
|
||||||
|
type: 'number',
|
||||||
validate: ({ value }) => {
|
validate: ({ value }) => {
|
||||||
return z.coerce
|
return z.coerce
|
||||||
.number()
|
.number()
|
||||||
@@ -1032,6 +1126,7 @@ export const envVariables: EnvVariableModel[] = [
|
|||||||
name: `ENABLE_REACT_COMPILER`,
|
name: `ENABLE_REACT_COMPILER`,
|
||||||
description: 'Enables the React compiler [experimental]',
|
description: 'Enables the React compiler [experimental]',
|
||||||
category: 'Build',
|
category: 'Build',
|
||||||
|
type: 'boolean',
|
||||||
validate: ({ value }) => {
|
validate: ({ value }) => {
|
||||||
return z.coerce.boolean().optional().safeParse(value);
|
return z.coerce.boolean().optional().safeParse(value);
|
||||||
},
|
},
|
||||||
@@ -1040,7 +1135,8 @@ export const envVariables: EnvVariableModel[] = [
|
|||||||
name: 'NEXT_PUBLIC_MONITORING_PROVIDER',
|
name: 'NEXT_PUBLIC_MONITORING_PROVIDER',
|
||||||
description: 'The monitoring provider to use.',
|
description: 'The monitoring provider to use.',
|
||||||
category: 'Monitoring',
|
category: 'Monitoring',
|
||||||
required: true,
|
type: 'enum',
|
||||||
|
values: ['baselime', 'sentry', 'none'],
|
||||||
validate: ({ value }) => {
|
validate: ({ value }) => {
|
||||||
return z.enum(['baselime', 'sentry', '']).optional().safeParse(value);
|
return z.enum(['baselime', 'sentry', '']).optional().safeParse(value);
|
||||||
},
|
},
|
||||||
@@ -1049,6 +1145,7 @@ export const envVariables: EnvVariableModel[] = [
|
|||||||
name: 'NEXT_PUBLIC_SENTRY_DSN',
|
name: 'NEXT_PUBLIC_SENTRY_DSN',
|
||||||
description: 'The Sentry DSN to use.',
|
description: 'The Sentry DSN to use.',
|
||||||
category: 'Monitoring',
|
category: 'Monitoring',
|
||||||
|
type: `string`,
|
||||||
contextualValidation: {
|
contextualValidation: {
|
||||||
dependencies: [
|
dependencies: [
|
||||||
{
|
{
|
||||||
@@ -1073,7 +1170,7 @@ export const envVariables: EnvVariableModel[] = [
|
|||||||
name: 'NEXT_PUBLIC_SENTRY_ENVIRONMENT',
|
name: 'NEXT_PUBLIC_SENTRY_ENVIRONMENT',
|
||||||
description: 'The Sentry environment to use.',
|
description: 'The Sentry environment to use.',
|
||||||
category: 'Monitoring',
|
category: 'Monitoring',
|
||||||
required: true,
|
type: 'string',
|
||||||
validate: ({ value }) => {
|
validate: ({ value }) => {
|
||||||
return z.string().optional().safeParse(value);
|
return z.string().optional().safeParse(value);
|
||||||
},
|
},
|
||||||
@@ -1082,6 +1179,7 @@ export const envVariables: EnvVariableModel[] = [
|
|||||||
name: 'NEXT_PUBLIC_BASELIME_KEY',
|
name: 'NEXT_PUBLIC_BASELIME_KEY',
|
||||||
description: 'The Baselime key to use.',
|
description: 'The Baselime key to use.',
|
||||||
category: 'Monitoring',
|
category: 'Monitoring',
|
||||||
|
type: 'string',
|
||||||
contextualValidation: {
|
contextualValidation: {
|
||||||
dependencies: [
|
dependencies: [
|
||||||
{
|
{
|
||||||
@@ -1107,6 +1205,7 @@ export const envVariables: EnvVariableModel[] = [
|
|||||||
name: 'STRIPE_ENABLE_TRIAL_WITHOUT_CC',
|
name: 'STRIPE_ENABLE_TRIAL_WITHOUT_CC',
|
||||||
description: 'Enables trial plans without credit card.',
|
description: 'Enables trial plans without credit card.',
|
||||||
category: 'Billing',
|
category: 'Billing',
|
||||||
|
type: 'boolean',
|
||||||
validate: ({ value }) => {
|
validate: ({ value }) => {
|
||||||
return z.coerce.boolean().optional().safeParse(value);
|
return z.coerce.boolean().optional().safeParse(value);
|
||||||
},
|
},
|
||||||
@@ -1115,6 +1214,7 @@ export const envVariables: EnvVariableModel[] = [
|
|||||||
name: 'NEXT_PUBLIC_THEME_COLOR',
|
name: 'NEXT_PUBLIC_THEME_COLOR',
|
||||||
description: 'The default theme color.',
|
description: 'The default theme color.',
|
||||||
category: 'Theme',
|
category: 'Theme',
|
||||||
|
type: 'string',
|
||||||
required: true,
|
required: true,
|
||||||
validate: ({ value }) => {
|
validate: ({ value }) => {
|
||||||
return z
|
return z
|
||||||
@@ -1132,6 +1232,7 @@ export const envVariables: EnvVariableModel[] = [
|
|||||||
description: 'The default theme color for dark mode.',
|
description: 'The default theme color for dark mode.',
|
||||||
category: 'Theme',
|
category: 'Theme',
|
||||||
required: true,
|
required: true,
|
||||||
|
type: 'string',
|
||||||
validate: ({ value }) => {
|
validate: ({ value }) => {
|
||||||
return z
|
return z
|
||||||
.string()
|
.string()
|
||||||
@@ -1147,6 +1248,17 @@ export const envVariables: EnvVariableModel[] = [
|
|||||||
name: 'NEXT_PUBLIC_DISPLAY_TERMS_AND_CONDITIONS_CHECKBOX',
|
name: 'NEXT_PUBLIC_DISPLAY_TERMS_AND_CONDITIONS_CHECKBOX',
|
||||||
description: 'Whether to display the terms checkbox during sign-up.',
|
description: 'Whether to display the terms checkbox during sign-up.',
|
||||||
category: 'Features',
|
category: 'Features',
|
||||||
|
type: 'boolean',
|
||||||
|
validate: ({ value }) => {
|
||||||
|
return z.coerce.boolean().optional().safeParse(value);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'ENABLE_STRICT_CSP',
|
||||||
|
description: 'Enables strict Content Security Policy (CSP) headers.',
|
||||||
|
category: 'Security',
|
||||||
|
required: false,
|
||||||
|
type: 'boolean',
|
||||||
validate: ({ value }) => {
|
validate: ({ value }) => {
|
||||||
return z.coerce.boolean().optional().safeParse(value);
|
return z.coerce.boolean().optional().safeParse(value);
|
||||||
},
|
},
|
||||||
|
|||||||
84
apps/dev-tool/app/variables/lib/server-actions.ts
Normal file
84
apps/dev-tool/app/variables/lib/server-actions.ts
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
'use server';
|
||||||
|
|
||||||
|
import { revalidatePath } from 'next/cache';
|
||||||
|
|
||||||
|
import { envVariables } from '@/app/variables/lib/env-variables-model';
|
||||||
|
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
||||||
|
import { resolve } from 'node:url';
|
||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
const Schema = z.object({
|
||||||
|
name: z.string().min(1),
|
||||||
|
value: z.string(),
|
||||||
|
mode: z.enum(['development', 'production']),
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the environment variable in the specified file.
|
||||||
|
* @param props
|
||||||
|
*/
|
||||||
|
export async function updateEnvironmentVariableAction(
|
||||||
|
props: z.infer<typeof Schema>,
|
||||||
|
) {
|
||||||
|
// Validate the input
|
||||||
|
const { name, mode, value } = Schema.parse(props);
|
||||||
|
const root = resolve(process.cwd(), '..');
|
||||||
|
const model = envVariables.find((item) => item.name === name);
|
||||||
|
|
||||||
|
// Determine the source file based on the mode
|
||||||
|
const source = (() => {
|
||||||
|
const isSecret = model?.secret ?? true;
|
||||||
|
|
||||||
|
switch (mode) {
|
||||||
|
case 'development':
|
||||||
|
if (isSecret) {
|
||||||
|
return '.env.local';
|
||||||
|
} else {
|
||||||
|
return '.env.development';
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'production':
|
||||||
|
if (isSecret) {
|
||||||
|
return '.env.production.local';
|
||||||
|
} else {
|
||||||
|
return '.env.production';
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new Error(`Invalid mode: ${mode}`);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
// check file exists, if not, create it
|
||||||
|
const filePath = `${root}/apps/web/${source}`;
|
||||||
|
|
||||||
|
if (!existsSync(filePath)) {
|
||||||
|
writeFileSync(filePath, '', 'utf-8');
|
||||||
|
}
|
||||||
|
|
||||||
|
const sourceEnvFile = readFileSync(`${root}apps/web/${source}`, 'utf-8');
|
||||||
|
|
||||||
|
let updatedEnvFile = '';
|
||||||
|
const isInSourceFile = sourceEnvFile.includes(name);
|
||||||
|
const isCommentedOut = sourceEnvFile.includes(`#${name}=`);
|
||||||
|
|
||||||
|
if (isInSourceFile && !isCommentedOut) {
|
||||||
|
updatedEnvFile = sourceEnvFile.replace(
|
||||||
|
new RegExp(`^${name}=.*`, 'm'),
|
||||||
|
`${name}=${value}`,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// if the key does not exist, append it to the end of the file
|
||||||
|
updatedEnvFile = `${sourceEnvFile}\n${name}=${value}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// write the updated content back to the file
|
||||||
|
writeFileSync(`${root}/apps/web/${source}`, updatedEnvFile, 'utf-8');
|
||||||
|
|
||||||
|
revalidatePath(`/variables`);
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: `Updated ${name} in "${source}"`,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -19,6 +19,13 @@ export type EnvVariableState = {
|
|||||||
effectiveValue: string;
|
effectiveValue: string;
|
||||||
isOverridden: boolean;
|
isOverridden: boolean;
|
||||||
effectiveSource: string;
|
effectiveSource: string;
|
||||||
|
isVisible: boolean;
|
||||||
|
validation: {
|
||||||
|
success: boolean;
|
||||||
|
error: {
|
||||||
|
issues: string[];
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export type AppEnvState = {
|
export type AppEnvState = {
|
||||||
|
|||||||
@@ -25,31 +25,30 @@ export default function VariablesPage({ searchParams }: VariablesPageProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Page style={'custom'}>
|
<Page style={'custom'}>
|
||||||
<PageHeader
|
<div className={'flex h-screen flex-col overflow-hidden'}>
|
||||||
displaySidebarTrigger={false}
|
<PageHeader
|
||||||
description={
|
displaySidebarTrigger={false}
|
||||||
<AppBreadcrumbs
|
title={'Environment Variables'}
|
||||||
values={{
|
description={
|
||||||
variables: 'Environment Variables',
|
'Manage environment variables for your applications. Validate and set them up easily.'
|
||||||
}}
|
}
|
||||||
/>
|
/>
|
||||||
}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<PageBody>
|
<PageBody className={'overflow-hidden'}>
|
||||||
<div className={'flex flex-col space-y-4 pb-16'}>
|
<div className={'flex h-full flex-1 flex-col space-y-4'}>
|
||||||
{apps.map((app) => {
|
{apps.map((app) => {
|
||||||
const appEnvState = processEnvDefinitions(app, mode);
|
const appEnvState = processEnvDefinitions(app, mode);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AppEnvironmentVariablesManager
|
<AppEnvironmentVariablesManager
|
||||||
key={app.appName}
|
key={app.appName}
|
||||||
state={appEnvState}
|
state={appEnvState}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
</PageBody>
|
</PageBody>
|
||||||
|
</div>
|
||||||
</Page>
|
</Page>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,13 +8,16 @@
|
|||||||
"format": "prettier --check --write \"**/*.{js,cjs,mjs,ts,tsx,md,json}\""
|
"format": "prettier --check --write \"**/*.{js,cjs,mjs,ts,tsx,md,json}\""
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@ai-sdk/openai": "^1.3.20",
|
||||||
"@hookform/resolvers": "^5.0.1",
|
"@hookform/resolvers": "^5.0.1",
|
||||||
"@tanstack/react-query": "5.74.4",
|
"@tanstack/react-query": "5.74.4",
|
||||||
|
"ai": "4.3.10",
|
||||||
"lucide-react": "^0.503.0",
|
"lucide-react": "^0.503.0",
|
||||||
"next": "15.3.1",
|
"next": "15.3.1",
|
||||||
"nodemailer": "^6.10.1",
|
"nodemailer": "^6.10.1",
|
||||||
"react": "19.1.0",
|
"react": "19.1.0",
|
||||||
"react-dom": "19.1.0"
|
"react-dom": "19.1.0",
|
||||||
|
"rxjs": "^7.8.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@kit/email-templates": "workspace:*",
|
"@kit/email-templates": "workspace:*",
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ const defaultLanguage = process.env.NEXT_PUBLIC_DEFAULT_LOCALE ?? 'en';
|
|||||||
* By default, only the default language is supported.
|
* By default, only the default language is supported.
|
||||||
* Add more languages here if needed.
|
* Add more languages here if needed.
|
||||||
*/
|
*/
|
||||||
export const languages: string[] = [defaultLanguage];
|
export const languages: string[] = [defaultLanguage, 'it'];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The name of the cookie that stores the selected language.
|
* The name of the cookie that stores the selected language.
|
||||||
|
|||||||
@@ -59,6 +59,7 @@
|
|||||||
"@supabase/supabase-js": "2.49.4",
|
"@supabase/supabase-js": "2.49.4",
|
||||||
"@tanstack/react-query": "5.74.4",
|
"@tanstack/react-query": "5.74.4",
|
||||||
"@tanstack/react-table": "^8.21.3",
|
"@tanstack/react-table": "^8.21.3",
|
||||||
|
"ai": "4.3.10",
|
||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.1.0",
|
||||||
"lucide-react": "^0.503.0",
|
"lucide-react": "^0.503.0",
|
||||||
"next": "15.3.1",
|
"next": "15.3.1",
|
||||||
|
|||||||
@@ -88,7 +88,7 @@
|
|||||||
"errorSendingCode": "Error sending code. Please try again."
|
"errorSendingCode": "Error sending code. Please try again."
|
||||||
},
|
},
|
||||||
"cookieBanner": {
|
"cookieBanner": {
|
||||||
"title": "Hey, we use cookies \uD83C\uDF6A",
|
"title": "Hey, we use cookies 🍪",
|
||||||
"description": "This website uses cookies to ensure you get the best experience on our website.",
|
"description": "This website uses cookies to ensure you get the best experience on our website.",
|
||||||
"reject": "Reject",
|
"reject": "Reject",
|
||||||
"accept": "Accept"
|
"accept": "Accept"
|
||||||
|
|||||||
@@ -5,22 +5,17 @@ import { type VariantProps, cva } from 'class-variance-authority';
|
|||||||
import { cn } from '../lib/utils';
|
import { cn } from '../lib/utils';
|
||||||
|
|
||||||
const badgeVariants = cva(
|
const badgeVariants = cva(
|
||||||
'focus:ring-ring inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:ring-2 focus:ring-offset-2 focus:outline-hidden',
|
'focus:ring-ring inline-flex items-center rounded-md border px-1.5 py-0.5 text-xs font-semibold transition-colors focus:ring-2 focus:ring-offset-2 focus:outline-hidden',
|
||||||
{
|
{
|
||||||
variants: {
|
variants: {
|
||||||
variant: {
|
variant: {
|
||||||
default:
|
default: 'bg-primary text-primary-foreground border-transparent',
|
||||||
'bg-primary text-primary-foreground hover:bg-primary/80 border-transparent shadow-xs',
|
secondary: 'bg-secondary text-secondary-foreground border-transparent',
|
||||||
secondary:
|
destructive: 'text-destructive border-destructive',
|
||||||
'bg-secondary text-secondary-foreground hover:bg-secondary/80 border-transparent',
|
|
||||||
destructive:
|
|
||||||
'bg-destructive text-destructive-foreground hover:bg-destructive/80 border-transparent shadow-xs',
|
|
||||||
outline: 'text-foreground',
|
outline: 'text-foreground',
|
||||||
success:
|
success: 'border-green-500 text-green-500',
|
||||||
'border-transparent bg-green-50 text-green-500 hover:bg-green-50 dark:bg-green-500/20 dark:hover:bg-green-500/20',
|
warning: 'border-orange-500 text-orange-500',
|
||||||
warning:
|
info: 'border-blue-500 text-blue-500',
|
||||||
'border-transparent bg-orange-50 text-orange-500 hover:bg-orange-50 dark:bg-orange-500/20 dark:hover:bg-orange-500/20',
|
|
||||||
info: 'border-transparent bg-blue-50 text-blue-500 hover:bg-blue-50 dark:bg-blue-500/20 dark:hover:bg-blue-500/20',
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
defaultVariants: {
|
defaultVariants: {
|
||||||
|
|||||||
307
pnpm-lock.yaml
generated
307
pnpm-lock.yaml
generated
@@ -33,12 +33,18 @@ importers:
|
|||||||
|
|
||||||
apps/dev-tool:
|
apps/dev-tool:
|
||||||
dependencies:
|
dependencies:
|
||||||
|
'@ai-sdk/openai':
|
||||||
|
specifier: ^1.3.20
|
||||||
|
version: 1.3.20(zod@3.24.3)
|
||||||
'@hookform/resolvers':
|
'@hookform/resolvers':
|
||||||
specifier: ^5.0.1
|
specifier: ^5.0.1
|
||||||
version: 5.0.1(react-hook-form@7.56.1(react@19.1.0))
|
version: 5.0.1(react-hook-form@7.56.1(react@19.1.0))
|
||||||
'@tanstack/react-query':
|
'@tanstack/react-query':
|
||||||
specifier: 5.74.4
|
specifier: 5.74.4
|
||||||
version: 5.74.4(react@19.1.0)
|
version: 5.74.4(react@19.1.0)
|
||||||
|
ai:
|
||||||
|
specifier: 4.3.10
|
||||||
|
version: 4.3.10(react@19.1.0)(zod@3.24.3)
|
||||||
lucide-react:
|
lucide-react:
|
||||||
specifier: ^0.503.0
|
specifier: ^0.503.0
|
||||||
version: 0.503.0(react@19.1.0)
|
version: 0.503.0(react@19.1.0)
|
||||||
@@ -54,6 +60,9 @@ importers:
|
|||||||
react-dom:
|
react-dom:
|
||||||
specifier: 19.1.0
|
specifier: 19.1.0
|
||||||
version: 19.1.0(react@19.1.0)
|
version: 19.1.0(react@19.1.0)
|
||||||
|
rxjs:
|
||||||
|
specifier: ^7.8.2
|
||||||
|
version: 7.8.2
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@kit/email-templates':
|
'@kit/email-templates':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
@@ -211,6 +220,9 @@ importers:
|
|||||||
'@tanstack/react-table':
|
'@tanstack/react-table':
|
||||||
specifier: ^8.21.3
|
specifier: ^8.21.3
|
||||||
version: 8.21.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
version: 8.21.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||||
|
ai:
|
||||||
|
specifier: 4.3.10
|
||||||
|
version: 4.3.10(react@19.1.0)(zod@3.24.3)
|
||||||
date-fns:
|
date-fns:
|
||||||
specifier: ^4.1.0
|
specifier: ^4.1.0
|
||||||
version: 4.1.0
|
version: 4.1.0
|
||||||
@@ -1559,13 +1571,13 @@ importers:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@trivago/prettier-plugin-sort-imports':
|
'@trivago/prettier-plugin-sort-imports':
|
||||||
specifier: 5.2.2
|
specifier: 5.2.2
|
||||||
version: 5.2.2(prettier@3.5.3)
|
version: 5.2.2(@vue/compiler-sfc@3.5.13)(prettier@3.5.3)(svelte@4.2.19)
|
||||||
prettier:
|
prettier:
|
||||||
specifier: ^3.5.3
|
specifier: ^3.5.3
|
||||||
version: 3.5.3
|
version: 3.5.3
|
||||||
prettier-plugin-tailwindcss:
|
prettier-plugin-tailwindcss:
|
||||||
specifier: ^0.6.11
|
specifier: ^0.6.11
|
||||||
version: 0.6.11(@trivago/prettier-plugin-sort-imports@5.2.2(prettier@3.5.3))(prettier@3.5.3)
|
version: 0.6.11(@trivago/prettier-plugin-sort-imports@5.2.2(@vue/compiler-sfc@3.5.13)(prettier@3.5.3)(svelte@4.2.19))(prettier@3.5.3)
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@kit/tsconfig':
|
'@kit/tsconfig':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
@@ -1588,6 +1600,38 @@ packages:
|
|||||||
graphql:
|
graphql:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
'@ai-sdk/openai@1.3.20':
|
||||||
|
resolution: {integrity: sha512-/DflUy7ROG9k6n6YTXMBFPbujBKnbGY58f3CwvicLvDar9nDAloVnUWd3LUoOxpSVnX8vtQ7ngxF52SLWO6RwQ==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
peerDependencies:
|
||||||
|
zod: ^3.0.0
|
||||||
|
|
||||||
|
'@ai-sdk/provider-utils@2.2.7':
|
||||||
|
resolution: {integrity: sha512-kM0xS3GWg3aMChh9zfeM+80vEZfXzR3JEUBdycZLtbRZ2TRT8xOj3WodGHPb06sUK5yD7pAXC/P7ctsi2fvUGQ==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
peerDependencies:
|
||||||
|
zod: ^3.23.8
|
||||||
|
|
||||||
|
'@ai-sdk/provider@1.1.3':
|
||||||
|
resolution: {integrity: sha512-qZMxYJ0qqX/RfnuIaab+zp8UAeJn/ygXXAffR5I4N0n1IrvA6qBsjc8hXLmBiMV2zoXlifkacF7sEFnYnjBcqg==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
|
'@ai-sdk/react@1.2.9':
|
||||||
|
resolution: {integrity: sha512-/VYm8xifyngaqFDLXACk/1czDRCefNCdALUyp+kIX6DUIYUWTM93ISoZ+qJ8+3E+FiJAKBQz61o8lIIl+vYtzg==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
peerDependencies:
|
||||||
|
react: ^18 || ^19 || ^19.0.0-rc
|
||||||
|
zod: ^3.23.8
|
||||||
|
peerDependenciesMeta:
|
||||||
|
zod:
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@ai-sdk/ui-utils@1.2.8':
|
||||||
|
resolution: {integrity: sha512-nls/IJCY+ks3Uj6G/agNhXqQeLVqhNfoJbuNgCny+nX2veY5ADB91EcZUqVeQ/ionul2SeUswPY6Q/DxteY29Q==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
peerDependencies:
|
||||||
|
zod: ^3.23.8
|
||||||
|
|
||||||
'@alloc/quick-lru@5.2.0':
|
'@alloc/quick-lru@5.2.0':
|
||||||
resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==}
|
resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
@@ -4325,6 +4369,9 @@ packages:
|
|||||||
'@types/debug@4.1.12':
|
'@types/debug@4.1.12':
|
||||||
resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==}
|
resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==}
|
||||||
|
|
||||||
|
'@types/diff-match-patch@1.0.36':
|
||||||
|
resolution: {integrity: sha512-xFdR6tkm0MWvBfO8xXCSsinYxHcqkQUlcHeSpMC2ukzOb6lwQAfDmW+Qt0AvlGd8HpsS28qKsB+oPeJn9I39jg==}
|
||||||
|
|
||||||
'@types/eslint-scope@3.7.7':
|
'@types/eslint-scope@3.7.7':
|
||||||
resolution: {integrity: sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==}
|
resolution: {integrity: sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==}
|
||||||
|
|
||||||
@@ -4621,6 +4668,21 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@urql/core': ^5.0.0
|
'@urql/core': ^5.0.0
|
||||||
|
|
||||||
|
'@vue/compiler-core@3.5.13':
|
||||||
|
resolution: {integrity: sha512-oOdAkwqUfW1WqpwSYJce06wvt6HljgY3fGeM9NcVA1HaYOij3mZG9Rkysn0OHuyUAGMbEbARIpsG+LPVlBJ5/Q==}
|
||||||
|
|
||||||
|
'@vue/compiler-dom@3.5.13':
|
||||||
|
resolution: {integrity: sha512-ZOJ46sMOKUjO3e94wPdCzQ6P1Lx/vhp2RSvfaab88Ajexs0AHeV0uasYhi99WPaogmBlRHNRuly8xV75cNTMDA==}
|
||||||
|
|
||||||
|
'@vue/compiler-sfc@3.5.13':
|
||||||
|
resolution: {integrity: sha512-6VdaljMpD82w6c2749Zhf5T9u5uLBWKnVue6XWxprDobftnletJ8+oel7sexFfM3qIxNmVE7LSFGTpv6obNyaQ==}
|
||||||
|
|
||||||
|
'@vue/compiler-ssr@3.5.13':
|
||||||
|
resolution: {integrity: sha512-wMH6vrYHxQl/IybKJagqbquvxpWCuVYpoUJfCqFZwa/JY1GdATAQ+TgVtgrwwMZ0D07QhA99rs/EAAWfvG6KpA==}
|
||||||
|
|
||||||
|
'@vue/shared@3.5.13':
|
||||||
|
resolution: {integrity: sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==}
|
||||||
|
|
||||||
'@webassemblyjs/ast@1.14.1':
|
'@webassemblyjs/ast@1.14.1':
|
||||||
resolution: {integrity: sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==}
|
resolution: {integrity: sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==}
|
||||||
|
|
||||||
@@ -4709,6 +4771,16 @@ packages:
|
|||||||
resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==}
|
resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
|
ai@4.3.10:
|
||||||
|
resolution: {integrity: sha512-jw+ahNu+T4SHj9gtraIKtYhanJI6gj2IZ5BFcfEHgoyQVMln5a5beGjzl/nQSX6FxyLqJ/UBpClRa279EEKK/Q==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
peerDependencies:
|
||||||
|
react: ^18 || ^19 || ^19.0.0-rc
|
||||||
|
zod: ^3.23.8
|
||||||
|
peerDependenciesMeta:
|
||||||
|
react:
|
||||||
|
optional: true
|
||||||
|
|
||||||
ajv-formats@2.1.1:
|
ajv-formats@2.1.1:
|
||||||
resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==}
|
resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -4930,6 +5002,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
|
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
|
chalk@5.4.1:
|
||||||
|
resolution: {integrity: sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==}
|
||||||
|
engines: {node: ^12.17.0 || ^14.13 || >=16.0.0}
|
||||||
|
|
||||||
change-case@3.1.0:
|
change-case@3.1.0:
|
||||||
resolution: {integrity: sha512-2AZp7uJZbYEzRPsFoa+ijKdvp9zsrnnt6+yFokfwEpeJm0xuJDVoxiRCAaTzyJND8GJkofo2IcKWaUZ/OECVzw==}
|
resolution: {integrity: sha512-2AZp7uJZbYEzRPsFoa+ijKdvp9zsrnnt6+yFokfwEpeJm0xuJDVoxiRCAaTzyJND8GJkofo2IcKWaUZ/OECVzw==}
|
||||||
|
|
||||||
@@ -5007,6 +5083,9 @@ packages:
|
|||||||
react: ^18 || ^19 || ^19.0.0-rc
|
react: ^18 || ^19 || ^19.0.0-rc
|
||||||
react-dom: ^18 || ^19 || ^19.0.0-rc
|
react-dom: ^18 || ^19 || ^19.0.0-rc
|
||||||
|
|
||||||
|
code-red@1.0.4:
|
||||||
|
resolution: {integrity: sha512-7qJWqItLA8/VPVlKJlFXU+NBlo/qyfs39aJcuMT/2ere32ZqvF5OSxgdM5xOfJJ7O429gg2HM47y8v9P+9wrNw==}
|
||||||
|
|
||||||
color-convert@1.9.3:
|
color-convert@1.9.3:
|
||||||
resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==}
|
resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==}
|
||||||
|
|
||||||
@@ -5300,6 +5379,9 @@ packages:
|
|||||||
devlop@1.1.0:
|
devlop@1.1.0:
|
||||||
resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==}
|
resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==}
|
||||||
|
|
||||||
|
diff-match-patch@1.0.5:
|
||||||
|
resolution: {integrity: sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==}
|
||||||
|
|
||||||
diff@4.0.2:
|
diff@4.0.2:
|
||||||
resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==}
|
resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==}
|
||||||
engines: {node: '>=0.3.1'}
|
engines: {node: '>=0.3.1'}
|
||||||
@@ -5582,6 +5664,9 @@ packages:
|
|||||||
estree-walker@2.0.2:
|
estree-walker@2.0.2:
|
||||||
resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
|
resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
|
||||||
|
|
||||||
|
estree-walker@3.0.3:
|
||||||
|
resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==}
|
||||||
|
|
||||||
esutils@2.0.3:
|
esutils@2.0.3:
|
||||||
resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
|
resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
@@ -6129,6 +6214,9 @@ packages:
|
|||||||
is-reference@1.2.1:
|
is-reference@1.2.1:
|
||||||
resolution: {integrity: sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==}
|
resolution: {integrity: sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==}
|
||||||
|
|
||||||
|
is-reference@3.0.3:
|
||||||
|
resolution: {integrity: sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==}
|
||||||
|
|
||||||
is-regex@1.2.1:
|
is-regex@1.2.1:
|
||||||
resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==}
|
resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
@@ -6242,6 +6330,9 @@ packages:
|
|||||||
json-schema-traverse@1.0.0:
|
json-schema-traverse@1.0.0:
|
||||||
resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==}
|
resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==}
|
||||||
|
|
||||||
|
json-schema@0.4.0:
|
||||||
|
resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==}
|
||||||
|
|
||||||
json-stable-stringify-without-jsonify@1.0.1:
|
json-stable-stringify-without-jsonify@1.0.1:
|
||||||
resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==}
|
resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==}
|
||||||
|
|
||||||
@@ -6254,6 +6345,11 @@ packages:
|
|||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
jsondiffpatch@0.6.0:
|
||||||
|
resolution: {integrity: sha512-3QItJOXp2AP1uv7waBkao5nCvhEv+QmJAd38Ybq7wNI74Q+BBmnLn4EDKz6yI9xGAIQoUF87qHt+kc1IVxB4zQ==}
|
||||||
|
engines: {node: ^18.0.0 || >=20.0.0}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
jsonfile@6.1.0:
|
jsonfile@6.1.0:
|
||||||
resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==}
|
resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==}
|
||||||
|
|
||||||
@@ -6365,6 +6461,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==}
|
resolution: {integrity: sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==}
|
||||||
engines: {node: '>=6.11.5'}
|
engines: {node: '>=6.11.5'}
|
||||||
|
|
||||||
|
locate-character@3.0.0:
|
||||||
|
resolution: {integrity: sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==}
|
||||||
|
|
||||||
locate-path@6.0.0:
|
locate-path@6.0.0:
|
||||||
resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
|
resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
@@ -6938,6 +7037,9 @@ packages:
|
|||||||
peberminta@0.9.0:
|
peberminta@0.9.0:
|
||||||
resolution: {integrity: sha512-XIxfHpEuSJbITd1H3EeQwpcZbTLHc+VVr8ANI9t5sit565tsI4/xK3KWTUFE2e6QiangUkh3B0jihzmGnNrRsQ==}
|
resolution: {integrity: sha512-XIxfHpEuSJbITd1H3EeQwpcZbTLHc+VVr8ANI9t5sit565tsI4/xK3KWTUFE2e6QiangUkh3B0jihzmGnNrRsQ==}
|
||||||
|
|
||||||
|
periscopic@3.1.0:
|
||||||
|
resolution: {integrity: sha512-vKiQ8RRtkl9P+r/+oefh25C3fhybptkHKCZSPlcXiJux2tJF55GnEj3BVn4A5gKfq9NWWXXrxkHBwVPUfH0opw==}
|
||||||
|
|
||||||
pg-int8@1.0.1:
|
pg-int8@1.0.1:
|
||||||
resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==}
|
resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==}
|
||||||
engines: {node: '>=4.0.0'}
|
engines: {node: '>=4.0.0'}
|
||||||
@@ -7851,6 +7953,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
|
resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
svelte@4.2.19:
|
||||||
|
resolution: {integrity: sha512-IY1rnGr6izd10B0A8LqsBfmlT5OILVuZ7XsI0vdGPEvuonFV7NYEUK4dAkm9Zg2q0Um92kYjTpS1CAP3Nh/KWw==}
|
||||||
|
engines: {node: '>=16'}
|
||||||
|
|
||||||
svgo@3.3.2:
|
svgo@3.3.2:
|
||||||
resolution: {integrity: sha512-OoohrmuUlBs8B8o6MB2Aevn+pRIH9zDALSR+6hhqVfa6fRwG/Qw9VUMSMW9VNg2CFc/MTIfabtdOVl9ODIJjpw==}
|
resolution: {integrity: sha512-OoohrmuUlBs8B8o6MB2Aevn+pRIH9zDALSR+6hhqVfa6fRwG/Qw9VUMSMW9VNg2CFc/MTIfabtdOVl9ODIJjpw==}
|
||||||
engines: {node: '>=14.0.0'}
|
engines: {node: '>=14.0.0'}
|
||||||
@@ -7859,6 +7965,11 @@ packages:
|
|||||||
swap-case@1.1.2:
|
swap-case@1.1.2:
|
||||||
resolution: {integrity: sha512-BAmWG6/bx8syfc6qXPprof3Mn5vQgf5dwdUNJhsNqU9WdPt5P+ES/wQ5bxfijy8zwZgZZHslC3iAsxsuQMCzJQ==}
|
resolution: {integrity: sha512-BAmWG6/bx8syfc6qXPprof3Mn5vQgf5dwdUNJhsNqU9WdPt5P+ES/wQ5bxfijy8zwZgZZHslC3iAsxsuQMCzJQ==}
|
||||||
|
|
||||||
|
swr@2.3.3:
|
||||||
|
resolution: {integrity: sha512-dshNvs3ExOqtZ6kJBaAsabhPdHyeY4P2cKwRCniDVifBMoG/SVI7tfLWqPXriVspf2Rg4tPzXJTnwaihIeFw2A==}
|
||||||
|
peerDependencies:
|
||||||
|
react: ^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||||
|
|
||||||
tabbable@6.2.0:
|
tabbable@6.2.0:
|
||||||
resolution: {integrity: sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==}
|
resolution: {integrity: sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==}
|
||||||
|
|
||||||
@@ -7905,6 +8016,10 @@ packages:
|
|||||||
thread-stream@3.1.0:
|
thread-stream@3.1.0:
|
||||||
resolution: {integrity: sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==}
|
resolution: {integrity: sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==}
|
||||||
|
|
||||||
|
throttleit@2.1.0:
|
||||||
|
resolution: {integrity: sha512-nt6AMGKW1p/70DF/hGBdJB57B8Tspmbp5gfJ8ilhLnt7kkr2ye7hzD6NVG8GGErk2HWF34igrL2CXmNIkzKqKw==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
through@2.3.8:
|
through@2.3.8:
|
||||||
resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==}
|
resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==}
|
||||||
|
|
||||||
@@ -8349,6 +8464,11 @@ packages:
|
|||||||
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
|
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
|
zod-to-json-schema@3.24.5:
|
||||||
|
resolution: {integrity: sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g==}
|
||||||
|
peerDependencies:
|
||||||
|
zod: ^3.24.1
|
||||||
|
|
||||||
zod@3.24.3:
|
zod@3.24.3:
|
||||||
resolution: {integrity: sha512-HhY1oqzWCQWuUqvBFnsyrtZRhyPeR7SUGv+C4+MsisMuVfSPx8HpwWqH8tRahSlt6M3PiFAcoeFhZAqIXTxoSg==}
|
resolution: {integrity: sha512-HhY1oqzWCQWuUqvBFnsyrtZRhyPeR7SUGv+C4+MsisMuVfSPx8HpwWqH8tRahSlt6M3PiFAcoeFhZAqIXTxoSg==}
|
||||||
|
|
||||||
@@ -8361,6 +8481,40 @@ snapshots:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
graphql: 16.10.0
|
graphql: 16.10.0
|
||||||
|
|
||||||
|
'@ai-sdk/openai@1.3.20(zod@3.24.3)':
|
||||||
|
dependencies:
|
||||||
|
'@ai-sdk/provider': 1.1.3
|
||||||
|
'@ai-sdk/provider-utils': 2.2.7(zod@3.24.3)
|
||||||
|
zod: 3.24.3
|
||||||
|
|
||||||
|
'@ai-sdk/provider-utils@2.2.7(zod@3.24.3)':
|
||||||
|
dependencies:
|
||||||
|
'@ai-sdk/provider': 1.1.3
|
||||||
|
nanoid: 3.3.11
|
||||||
|
secure-json-parse: 2.7.0
|
||||||
|
zod: 3.24.3
|
||||||
|
|
||||||
|
'@ai-sdk/provider@1.1.3':
|
||||||
|
dependencies:
|
||||||
|
json-schema: 0.4.0
|
||||||
|
|
||||||
|
'@ai-sdk/react@1.2.9(react@19.1.0)(zod@3.24.3)':
|
||||||
|
dependencies:
|
||||||
|
'@ai-sdk/provider-utils': 2.2.7(zod@3.24.3)
|
||||||
|
'@ai-sdk/ui-utils': 1.2.8(zod@3.24.3)
|
||||||
|
react: 19.1.0
|
||||||
|
swr: 2.3.3(react@19.1.0)
|
||||||
|
throttleit: 2.1.0
|
||||||
|
optionalDependencies:
|
||||||
|
zod: 3.24.3
|
||||||
|
|
||||||
|
'@ai-sdk/ui-utils@1.2.8(zod@3.24.3)':
|
||||||
|
dependencies:
|
||||||
|
'@ai-sdk/provider': 1.1.3
|
||||||
|
'@ai-sdk/provider-utils': 2.2.7(zod@3.24.3)
|
||||||
|
zod: 3.24.3
|
||||||
|
zod-to-json-schema: 3.24.5(zod@3.24.3)
|
||||||
|
|
||||||
'@alloc/quick-lru@5.2.0': {}
|
'@alloc/quick-lru@5.2.0': {}
|
||||||
|
|
||||||
'@ampproject/remapping@2.3.0':
|
'@ampproject/remapping@2.3.0':
|
||||||
@@ -11741,7 +11895,7 @@ snapshots:
|
|||||||
|
|
||||||
'@tootallnate/quickjs-emscripten@0.23.0': {}
|
'@tootallnate/quickjs-emscripten@0.23.0': {}
|
||||||
|
|
||||||
'@trivago/prettier-plugin-sort-imports@5.2.2(prettier@3.5.3)':
|
'@trivago/prettier-plugin-sort-imports@5.2.2(@vue/compiler-sfc@3.5.13)(prettier@3.5.3)(svelte@4.2.19)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/generator': 7.27.0
|
'@babel/generator': 7.27.0
|
||||||
'@babel/parser': 7.27.0
|
'@babel/parser': 7.27.0
|
||||||
@@ -11750,6 +11904,9 @@ snapshots:
|
|||||||
javascript-natural-sort: 0.7.1
|
javascript-natural-sort: 0.7.1
|
||||||
lodash: 4.17.21
|
lodash: 4.17.21
|
||||||
prettier: 3.5.3
|
prettier: 3.5.3
|
||||||
|
optionalDependencies:
|
||||||
|
'@vue/compiler-sfc': 3.5.13
|
||||||
|
svelte: 4.2.19
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
@@ -11846,6 +12003,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@types/ms': 2.1.0
|
'@types/ms': 2.1.0
|
||||||
|
|
||||||
|
'@types/diff-match-patch@1.0.36': {}
|
||||||
|
|
||||||
'@types/eslint-scope@3.7.7':
|
'@types/eslint-scope@3.7.7':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/eslint': 9.6.1
|
'@types/eslint': 9.6.1
|
||||||
@@ -12189,6 +12348,43 @@ snapshots:
|
|||||||
'@urql/core': 5.1.1(graphql@16.10.0)
|
'@urql/core': 5.1.1(graphql@16.10.0)
|
||||||
wonka: 6.3.5
|
wonka: 6.3.5
|
||||||
|
|
||||||
|
'@vue/compiler-core@3.5.13':
|
||||||
|
dependencies:
|
||||||
|
'@babel/parser': 7.27.0
|
||||||
|
'@vue/shared': 3.5.13
|
||||||
|
entities: 4.5.0
|
||||||
|
estree-walker: 2.0.2
|
||||||
|
source-map-js: 1.2.1
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@vue/compiler-dom@3.5.13':
|
||||||
|
dependencies:
|
||||||
|
'@vue/compiler-core': 3.5.13
|
||||||
|
'@vue/shared': 3.5.13
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@vue/compiler-sfc@3.5.13':
|
||||||
|
dependencies:
|
||||||
|
'@babel/parser': 7.27.0
|
||||||
|
'@vue/compiler-core': 3.5.13
|
||||||
|
'@vue/compiler-dom': 3.5.13
|
||||||
|
'@vue/compiler-ssr': 3.5.13
|
||||||
|
'@vue/shared': 3.5.13
|
||||||
|
estree-walker: 2.0.2
|
||||||
|
magic-string: 0.30.17
|
||||||
|
postcss: 8.5.3
|
||||||
|
source-map-js: 1.2.1
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@vue/compiler-ssr@3.5.13':
|
||||||
|
dependencies:
|
||||||
|
'@vue/compiler-dom': 3.5.13
|
||||||
|
'@vue/shared': 3.5.13
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@vue/shared@3.5.13':
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@webassemblyjs/ast@1.14.1':
|
'@webassemblyjs/ast@1.14.1':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@webassemblyjs/helper-numbers': 1.13.2
|
'@webassemblyjs/helper-numbers': 1.13.2
|
||||||
@@ -12300,6 +12496,18 @@ snapshots:
|
|||||||
clean-stack: 2.2.0
|
clean-stack: 2.2.0
|
||||||
indent-string: 4.0.0
|
indent-string: 4.0.0
|
||||||
|
|
||||||
|
ai@4.3.10(react@19.1.0)(zod@3.24.3):
|
||||||
|
dependencies:
|
||||||
|
'@ai-sdk/provider': 1.1.3
|
||||||
|
'@ai-sdk/provider-utils': 2.2.7(zod@3.24.3)
|
||||||
|
'@ai-sdk/react': 1.2.9(react@19.1.0)(zod@3.24.3)
|
||||||
|
'@ai-sdk/ui-utils': 1.2.8(zod@3.24.3)
|
||||||
|
'@opentelemetry/api': 1.9.0
|
||||||
|
jsondiffpatch: 0.6.0
|
||||||
|
zod: 3.24.3
|
||||||
|
optionalDependencies:
|
||||||
|
react: 19.1.0
|
||||||
|
|
||||||
ajv-formats@2.1.1(ajv@8.17.1):
|
ajv-formats@2.1.1(ajv@8.17.1):
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
ajv: 8.17.1
|
ajv: 8.17.1
|
||||||
@@ -12565,6 +12773,8 @@ snapshots:
|
|||||||
ansi-styles: 4.3.0
|
ansi-styles: 4.3.0
|
||||||
supports-color: 7.2.0
|
supports-color: 7.2.0
|
||||||
|
|
||||||
|
chalk@5.4.1: {}
|
||||||
|
|
||||||
change-case@3.1.0:
|
change-case@3.1.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
camel-case: 3.0.0
|
camel-case: 3.0.0
|
||||||
@@ -12654,6 +12864,15 @@ snapshots:
|
|||||||
- '@types/react'
|
- '@types/react'
|
||||||
- '@types/react-dom'
|
- '@types/react-dom'
|
||||||
|
|
||||||
|
code-red@1.0.4:
|
||||||
|
dependencies:
|
||||||
|
'@jridgewell/sourcemap-codec': 1.5.0
|
||||||
|
'@types/estree': 1.0.7
|
||||||
|
acorn: 8.14.1
|
||||||
|
estree-walker: 3.0.3
|
||||||
|
periscopic: 3.1.0
|
||||||
|
optional: true
|
||||||
|
|
||||||
color-convert@1.9.3:
|
color-convert@1.9.3:
|
||||||
dependencies:
|
dependencies:
|
||||||
color-name: 1.1.3
|
color-name: 1.1.3
|
||||||
@@ -12951,6 +13170,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
dequal: 2.0.3
|
dequal: 2.0.3
|
||||||
|
|
||||||
|
diff-match-patch@1.0.5: {}
|
||||||
|
|
||||||
diff@4.0.2: {}
|
diff@4.0.2: {}
|
||||||
|
|
||||||
dir-glob@3.0.1:
|
dir-glob@3.0.1:
|
||||||
@@ -13151,8 +13372,8 @@ snapshots:
|
|||||||
'@typescript-eslint/parser': 8.30.1(eslint@9.25.1(jiti@2.4.2))(typescript@5.8.3)
|
'@typescript-eslint/parser': 8.30.1(eslint@9.25.1(jiti@2.4.2))(typescript@5.8.3)
|
||||||
eslint: 9.25.1(jiti@2.4.2)
|
eslint: 9.25.1(jiti@2.4.2)
|
||||||
eslint-import-resolver-node: 0.3.9
|
eslint-import-resolver-node: 0.3.9
|
||||||
eslint-import-resolver-typescript: 3.10.0(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.30.1(eslint@9.25.1(jiti@2.4.2))(typescript@5.8.3))(eslint@9.25.1(jiti@2.4.2)))(eslint@9.25.1(jiti@2.4.2))
|
eslint-import-resolver-typescript: 3.10.0(eslint-plugin-import@2.31.0)(eslint@9.25.1(jiti@2.4.2))
|
||||||
eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.30.1(eslint@9.25.1(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.0(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.30.1(eslint@9.25.1(jiti@2.4.2))(typescript@5.8.3))(eslint@9.25.1(jiti@2.4.2)))(eslint@9.25.1(jiti@2.4.2)))(eslint@9.25.1(jiti@2.4.2))
|
eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.31.0(eslint@9.25.1(jiti@2.4.2))(typescript@5.8.3))(eslint@9.25.1(jiti@2.4.2))
|
||||||
eslint-plugin-jsx-a11y: 6.10.2(eslint@9.25.1(jiti@2.4.2))
|
eslint-plugin-jsx-a11y: 6.10.2(eslint@9.25.1(jiti@2.4.2))
|
||||||
eslint-plugin-react: 7.37.5(eslint@9.25.1(jiti@2.4.2))
|
eslint-plugin-react: 7.37.5(eslint@9.25.1(jiti@2.4.2))
|
||||||
eslint-plugin-react-hooks: 5.2.0(eslint@9.25.1(jiti@2.4.2))
|
eslint-plugin-react-hooks: 5.2.0(eslint@9.25.1(jiti@2.4.2))
|
||||||
@@ -13177,7 +13398,7 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
eslint-import-resolver-typescript@3.10.0(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.30.1(eslint@9.25.1(jiti@2.4.2))(typescript@5.8.3))(eslint@9.25.1(jiti@2.4.2)))(eslint@9.25.1(jiti@2.4.2)):
|
eslint-import-resolver-typescript@3.10.0(eslint-plugin-import@2.31.0)(eslint@9.25.1(jiti@2.4.2)):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@nolyfill/is-core-module': 1.0.39
|
'@nolyfill/is-core-module': 1.0.39
|
||||||
debug: 4.4.0
|
debug: 4.4.0
|
||||||
@@ -13188,22 +13409,22 @@ snapshots:
|
|||||||
tinyglobby: 0.2.13
|
tinyglobby: 0.2.13
|
||||||
unrs-resolver: 1.6.1
|
unrs-resolver: 1.6.1
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.30.1(eslint@9.25.1(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.0(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.30.1(eslint@9.25.1(jiti@2.4.2))(typescript@5.8.3))(eslint@9.25.1(jiti@2.4.2)))(eslint@9.25.1(jiti@2.4.2)))(eslint@9.25.1(jiti@2.4.2))
|
eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.31.0(eslint@9.25.1(jiti@2.4.2))(typescript@5.8.3))(eslint@9.25.1(jiti@2.4.2))
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
eslint-module-utils@2.12.0(@typescript-eslint/parser@8.30.1(eslint@9.25.1(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.0(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.30.1(eslint@9.25.1(jiti@2.4.2))(typescript@5.8.3))(eslint@9.25.1(jiti@2.4.2)))(eslint@9.25.1(jiti@2.4.2)))(eslint@9.25.1(jiti@2.4.2)):
|
eslint-module-utils@2.12.0(@typescript-eslint/parser@8.30.1(eslint@9.25.1(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.0)(eslint@9.25.1(jiti@2.4.2)):
|
||||||
dependencies:
|
dependencies:
|
||||||
debug: 3.2.7
|
debug: 3.2.7
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@typescript-eslint/parser': 8.30.1(eslint@9.25.1(jiti@2.4.2))(typescript@5.8.3)
|
'@typescript-eslint/parser': 8.30.1(eslint@9.25.1(jiti@2.4.2))(typescript@5.8.3)
|
||||||
eslint: 9.25.1(jiti@2.4.2)
|
eslint: 9.25.1(jiti@2.4.2)
|
||||||
eslint-import-resolver-node: 0.3.9
|
eslint-import-resolver-node: 0.3.9
|
||||||
eslint-import-resolver-typescript: 3.10.0(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.30.1(eslint@9.25.1(jiti@2.4.2))(typescript@5.8.3))(eslint@9.25.1(jiti@2.4.2)))(eslint@9.25.1(jiti@2.4.2))
|
eslint-import-resolver-typescript: 3.10.0(eslint-plugin-import@2.31.0)(eslint@9.25.1(jiti@2.4.2))
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.30.1(eslint@9.25.1(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.0(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.30.1(eslint@9.25.1(jiti@2.4.2))(typescript@5.8.3))(eslint@9.25.1(jiti@2.4.2)))(eslint@9.25.1(jiti@2.4.2)))(eslint@9.25.1(jiti@2.4.2)):
|
eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.31.0(eslint@9.25.1(jiti@2.4.2))(typescript@5.8.3))(eslint@9.25.1(jiti@2.4.2)):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@rtsao/scc': 1.1.0
|
'@rtsao/scc': 1.1.0
|
||||||
array-includes: 3.1.8
|
array-includes: 3.1.8
|
||||||
@@ -13214,7 +13435,7 @@ snapshots:
|
|||||||
doctrine: 2.1.0
|
doctrine: 2.1.0
|
||||||
eslint: 9.25.1(jiti@2.4.2)
|
eslint: 9.25.1(jiti@2.4.2)
|
||||||
eslint-import-resolver-node: 0.3.9
|
eslint-import-resolver-node: 0.3.9
|
||||||
eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.30.1(eslint@9.25.1(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.0(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.30.1(eslint@9.25.1(jiti@2.4.2))(typescript@5.8.3))(eslint@9.25.1(jiti@2.4.2)))(eslint@9.25.1(jiti@2.4.2)))(eslint@9.25.1(jiti@2.4.2))
|
eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.30.1(eslint@9.25.1(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.0)(eslint@9.25.1(jiti@2.4.2))
|
||||||
hasown: 2.0.2
|
hasown: 2.0.2
|
||||||
is-core-module: 2.16.1
|
is-core-module: 2.16.1
|
||||||
is-glob: 4.0.3
|
is-glob: 4.0.3
|
||||||
@@ -13226,7 +13447,7 @@ snapshots:
|
|||||||
string.prototype.trimend: 1.0.9
|
string.prototype.trimend: 1.0.9
|
||||||
tsconfig-paths: 3.15.0
|
tsconfig-paths: 3.15.0
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@typescript-eslint/parser': 8.30.1(eslint@9.25.1(jiti@2.4.2))(typescript@5.8.3)
|
'@typescript-eslint/parser': 8.31.0(eslint@9.25.1(jiti@2.4.2))(typescript@5.8.3)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- eslint-import-resolver-typescript
|
- eslint-import-resolver-typescript
|
||||||
- eslint-import-resolver-webpack
|
- eslint-import-resolver-webpack
|
||||||
@@ -13368,6 +13589,11 @@ snapshots:
|
|||||||
|
|
||||||
estree-walker@2.0.2: {}
|
estree-walker@2.0.2: {}
|
||||||
|
|
||||||
|
estree-walker@3.0.3:
|
||||||
|
dependencies:
|
||||||
|
'@types/estree': 1.0.7
|
||||||
|
optional: true
|
||||||
|
|
||||||
esutils@2.0.3: {}
|
esutils@2.0.3: {}
|
||||||
|
|
||||||
event-target-shim@6.0.2: {}
|
event-target-shim@6.0.2: {}
|
||||||
@@ -13952,6 +14178,11 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@types/estree': 1.0.7
|
'@types/estree': 1.0.7
|
||||||
|
|
||||||
|
is-reference@3.0.3:
|
||||||
|
dependencies:
|
||||||
|
'@types/estree': 1.0.7
|
||||||
|
optional: true
|
||||||
|
|
||||||
is-regex@1.2.1:
|
is-regex@1.2.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
call-bound: 1.0.4
|
call-bound: 1.0.4
|
||||||
@@ -14050,6 +14281,8 @@ snapshots:
|
|||||||
|
|
||||||
json-schema-traverse@1.0.0: {}
|
json-schema-traverse@1.0.0: {}
|
||||||
|
|
||||||
|
json-schema@0.4.0: {}
|
||||||
|
|
||||||
json-stable-stringify-without-jsonify@1.0.1: {}
|
json-stable-stringify-without-jsonify@1.0.1: {}
|
||||||
|
|
||||||
json5@1.0.2:
|
json5@1.0.2:
|
||||||
@@ -14058,6 +14291,12 @@ snapshots:
|
|||||||
|
|
||||||
json5@2.2.3: {}
|
json5@2.2.3: {}
|
||||||
|
|
||||||
|
jsondiffpatch@0.6.0:
|
||||||
|
dependencies:
|
||||||
|
'@types/diff-match-patch': 1.0.36
|
||||||
|
chalk: 5.4.1
|
||||||
|
diff-match-patch: 1.0.5
|
||||||
|
|
||||||
jsonfile@6.1.0:
|
jsonfile@6.1.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
universalify: 2.0.1
|
universalify: 2.0.1
|
||||||
@@ -14147,6 +14386,9 @@ snapshots:
|
|||||||
|
|
||||||
loader-runner@4.3.0: {}
|
loader-runner@4.3.0: {}
|
||||||
|
|
||||||
|
locate-character@3.0.0:
|
||||||
|
optional: true
|
||||||
|
|
||||||
locate-path@6.0.0:
|
locate-path@6.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
p-locate: 5.0.0
|
p-locate: 5.0.0
|
||||||
@@ -14969,6 +15211,13 @@ snapshots:
|
|||||||
|
|
||||||
peberminta@0.9.0: {}
|
peberminta@0.9.0: {}
|
||||||
|
|
||||||
|
periscopic@3.1.0:
|
||||||
|
dependencies:
|
||||||
|
'@types/estree': 1.0.7
|
||||||
|
estree-walker: 3.0.3
|
||||||
|
is-reference: 3.0.3
|
||||||
|
optional: true
|
||||||
|
|
||||||
pg-int8@1.0.1: {}
|
pg-int8@1.0.1: {}
|
||||||
|
|
||||||
pg-protocol@1.8.0: {}
|
pg-protocol@1.8.0: {}
|
||||||
@@ -15220,11 +15469,11 @@ snapshots:
|
|||||||
|
|
||||||
prelude-ls@1.2.1: {}
|
prelude-ls@1.2.1: {}
|
||||||
|
|
||||||
prettier-plugin-tailwindcss@0.6.11(@trivago/prettier-plugin-sort-imports@5.2.2(prettier@3.5.3))(prettier@3.5.3):
|
prettier-plugin-tailwindcss@0.6.11(@trivago/prettier-plugin-sort-imports@5.2.2(@vue/compiler-sfc@3.5.13)(prettier@3.5.3)(svelte@4.2.19))(prettier@3.5.3):
|
||||||
dependencies:
|
dependencies:
|
||||||
prettier: 3.5.3
|
prettier: 3.5.3
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@trivago/prettier-plugin-sort-imports': 5.2.2(prettier@3.5.3)
|
'@trivago/prettier-plugin-sort-imports': 5.2.2(@vue/compiler-sfc@3.5.13)(prettier@3.5.3)(svelte@4.2.19)
|
||||||
|
|
||||||
prettier@3.5.3: {}
|
prettier@3.5.3: {}
|
||||||
|
|
||||||
@@ -15959,6 +16208,24 @@ snapshots:
|
|||||||
|
|
||||||
supports-preserve-symlinks-flag@1.0.0: {}
|
supports-preserve-symlinks-flag@1.0.0: {}
|
||||||
|
|
||||||
|
svelte@4.2.19:
|
||||||
|
dependencies:
|
||||||
|
'@ampproject/remapping': 2.3.0
|
||||||
|
'@jridgewell/sourcemap-codec': 1.5.0
|
||||||
|
'@jridgewell/trace-mapping': 0.3.25
|
||||||
|
'@types/estree': 1.0.7
|
||||||
|
acorn: 8.14.1
|
||||||
|
aria-query: 5.3.2
|
||||||
|
axobject-query: 4.1.0
|
||||||
|
code-red: 1.0.4
|
||||||
|
css-tree: 2.3.1
|
||||||
|
estree-walker: 3.0.3
|
||||||
|
is-reference: 3.0.3
|
||||||
|
locate-character: 3.0.0
|
||||||
|
magic-string: 0.30.17
|
||||||
|
periscopic: 3.1.0
|
||||||
|
optional: true
|
||||||
|
|
||||||
svgo@3.3.2:
|
svgo@3.3.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@trysound/sax': 0.2.0
|
'@trysound/sax': 0.2.0
|
||||||
@@ -15974,6 +16241,12 @@ snapshots:
|
|||||||
lower-case: 1.1.4
|
lower-case: 1.1.4
|
||||||
upper-case: 1.1.3
|
upper-case: 1.1.3
|
||||||
|
|
||||||
|
swr@2.3.3(react@19.1.0):
|
||||||
|
dependencies:
|
||||||
|
dequal: 2.0.3
|
||||||
|
react: 19.1.0
|
||||||
|
use-sync-external-store: 1.5.0(react@19.1.0)
|
||||||
|
|
||||||
tabbable@6.2.0: {}
|
tabbable@6.2.0: {}
|
||||||
|
|
||||||
tailwind-merge@3.2.0: {}
|
tailwind-merge@3.2.0: {}
|
||||||
@@ -16015,6 +16288,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
real-require: 0.2.0
|
real-require: 0.2.0
|
||||||
|
|
||||||
|
throttleit@2.1.0: {}
|
||||||
|
|
||||||
through@2.3.8: {}
|
through@2.3.8: {}
|
||||||
|
|
||||||
tiny-invariant@1.0.6: {}
|
tiny-invariant@1.0.6: {}
|
||||||
@@ -16519,6 +16794,10 @@ snapshots:
|
|||||||
|
|
||||||
yocto-queue@0.1.0: {}
|
yocto-queue@0.1.0: {}
|
||||||
|
|
||||||
|
zod-to-json-schema@3.24.5(zod@3.24.3):
|
||||||
|
dependencies:
|
||||||
|
zod: 3.24.3
|
||||||
|
|
||||||
zod@3.24.3: {}
|
zod@3.24.3: {}
|
||||||
|
|
||||||
zwitch@2.0.4: {}
|
zwitch@2.0.4: {}
|
||||||
|
|||||||
Reference in New Issue
Block a user