Files
moreminimore-vibe/src/lib/schemas.ts
Adeniji Adekunle James b1095b7951 feat: allow manual Node.js path configuration (#1577)
Add ability to manually configure Node.js path for users who have
Node.js
installed but not in their system PATH.

Features:
- Browse and select custom Node.js installation folder
- Visual status indicator showing Node.js version or "Not found"
- Reset to system default PATH option
- Manual configuration option in setup banner
- Real-time Node.js status checking

closes #1050

    
<!-- This is an auto-generated description by cubic. -->
---

## Summary by cubic
Adds manual Node.js path configuration so the app works even when Node
isn’t on PATH, fulfilling #1050. Users can browse to their install,
reset to default, and see real-time status in Settings and during setup.

- New Features
- Settings: NodePathSelector to browse a Node.js folder, show
version/“Not found” status, and reset to system PATH (persists
customNodePath).
- Setup banner: manual config flow with a folder picker if Node is
already installed.
- IPC: select-node-folder, set-node-path, get-node-path; reloads env and
prepends custom path to PATH.
- Real-time Node.js status check with visual indicator (CheckCircle on
valid).
  - E2E tests for browse, reset, and valid-status display.

<!-- End of auto-generated description by cubic. -->
2025-10-20 17:18:29 -07:00

366 lines
9.9 KiB
TypeScript

import { z } from "zod";
export const SecretSchema = z.object({
value: z.string(),
encryptionType: z.enum(["electron-safe-storage", "plaintext"]).optional(),
});
export type Secret = z.infer<typeof SecretSchema>;
/**
* Zod schema for chat summary objects returned by the get-chats IPC
*/
export const ChatSummarySchema = z.object({
id: z.number(),
appId: z.number(),
title: z.string().nullable(),
createdAt: z.date(),
});
/**
* Type derived from the ChatSummarySchema
*/
export type ChatSummary = z.infer<typeof ChatSummarySchema>;
/**
* Zod schema for an array of chat summaries
*/
export const ChatSummariesSchema = z.array(ChatSummarySchema);
/**
* Zod schema for chat search result objects returned by the search-chats IPC
*/
export const ChatSearchResultSchema = z.object({
id: z.number(),
appId: z.number(),
title: z.string().nullable(),
createdAt: z.date(),
matchedMessageContent: z.string().nullable(),
});
/**
* Type derived from the ChatSearchResultSchema
*/
export type ChatSearchResult = z.infer<typeof ChatSearchResultSchema>;
export const ChatSearchResultsSchema = z.array(ChatSearchResultSchema);
// Zod schema for app search result objects returned by the search-app IPC
export const AppSearchResultSchema = z.object({
id: z.number(),
name: z.string(),
createdAt: z.date(),
matchedChatTitle: z.string().nullable(),
matchedChatMessage: z.string().nullable(),
});
// Type derived from AppSearchResultSchema
export type AppSearchResult = z.infer<typeof AppSearchResultSchema>;
export const AppSearchResultsSchema = z.array(AppSearchResultSchema);
const providers = [
"openai",
"anthropic",
"google",
"vertex",
"auto",
"openrouter",
"ollama",
"lmstudio",
"azure",
"xai",
"bedrock",
] as const;
export const cloudProviders = providers.filter(
(provider) => provider !== "ollama" && provider !== "lmstudio",
);
/**
* Zod schema for large language model configuration
*/
export const LargeLanguageModelSchema = z.object({
name: z.string(),
provider: z.string(),
customModelId: z.number().optional(),
});
/**
* Type derived from the LargeLanguageModelSchema
*/
export type LargeLanguageModel = z.infer<typeof LargeLanguageModelSchema>;
/**
* Zod schema for provider settings
* Regular providers use only apiKey. Vertex has additional optional fields.
*/
export const RegularProviderSettingSchema = z.object({
apiKey: SecretSchema.optional(),
});
export const AzureProviderSettingSchema = z.object({
apiKey: SecretSchema.optional(),
resourceName: z.string().optional(),
});
export const VertexProviderSettingSchema = z.object({
// We make this undefined so that it makes existing callsites easier.
apiKey: z.undefined(),
projectId: z.string().optional(),
location: z.string().optional(),
serviceAccountKey: SecretSchema.optional(),
});
export const ProviderSettingSchema = z.union([
// Must use more specific type first!
// Zod uses the first type that matches.
//
// We use passthrough as a hack because Azure and Vertex
// will match together since their required fields overlap.
//
// In addition, there may be future provider settings that
// we may want to preserve (e.g. user downgrades to older version)
// so doing passthrough keeps these extra fields.
AzureProviderSettingSchema.passthrough(),
VertexProviderSettingSchema.passthrough(),
RegularProviderSettingSchema.passthrough(),
]);
/**
* Type derived from the ProviderSettingSchema
*/
export type ProviderSetting = z.infer<typeof ProviderSettingSchema>;
export type RegularProviderSetting = z.infer<
typeof RegularProviderSettingSchema
>;
export type AzureProviderSetting = z.infer<typeof AzureProviderSettingSchema>;
export type VertexProviderSetting = z.infer<typeof VertexProviderSettingSchema>;
export const RuntimeModeSchema = z.enum(["web-sandbox", "local-node", "unset"]);
export type RuntimeMode = z.infer<typeof RuntimeModeSchema>;
export const RuntimeMode2Schema = z.enum(["host", "docker"]);
export type RuntimeMode2 = z.infer<typeof RuntimeMode2Schema>;
export const ChatModeSchema = z.enum(["build", "ask", "agent"]);
export type ChatMode = z.infer<typeof ChatModeSchema>;
export const GitHubSecretsSchema = z.object({
accessToken: SecretSchema.nullable(),
});
export type GitHubSecrets = z.infer<typeof GitHubSecretsSchema>;
export const GithubUserSchema = z.object({
email: z.string(),
});
export type GithubUser = z.infer<typeof GithubUserSchema>;
export const SupabaseSchema = z.object({
accessToken: SecretSchema.optional(),
refreshToken: SecretSchema.optional(),
expiresIn: z.number().optional(),
tokenTimestamp: z.number().optional(),
});
export type Supabase = z.infer<typeof SupabaseSchema>;
export const NeonSchema = z.object({
accessToken: SecretSchema.optional(),
refreshToken: SecretSchema.optional(),
expiresIn: z.number().optional(),
tokenTimestamp: z.number().optional(),
});
export type Neon = z.infer<typeof NeonSchema>;
export const ExperimentsSchema = z.object({
// Deprecated
enableSupabaseIntegration: z.boolean().describe("DEPRECATED").optional(),
enableFileEditing: z.boolean().describe("DEPRECATED").optional(),
});
export type Experiments = z.infer<typeof ExperimentsSchema>;
export const DyadProBudgetSchema = z.object({
budgetResetAt: z.string(),
maxBudget: z.number(),
});
export type DyadProBudget = z.infer<typeof DyadProBudgetSchema>;
export const GlobPathSchema = z.object({
globPath: z.string(),
});
export type GlobPath = z.infer<typeof GlobPathSchema>;
export const AppChatContextSchema = z.object({
contextPaths: z.array(GlobPathSchema),
smartContextAutoIncludes: z.array(GlobPathSchema),
excludePaths: z.array(GlobPathSchema).optional(),
});
export type AppChatContext = z.infer<typeof AppChatContextSchema>;
export type ContextPathResult = GlobPath & {
files: number;
tokens: number;
};
export type ContextPathResults = {
contextPaths: ContextPathResult[];
smartContextAutoIncludes: ContextPathResult[];
excludePaths: ContextPathResult[];
};
export const ReleaseChannelSchema = z.enum(["stable", "beta"]);
export type ReleaseChannel = z.infer<typeof ReleaseChannelSchema>;
/**
* Zod schema for user settings
*/
export const UserSettingsSchema = z.object({
selectedModel: LargeLanguageModelSchema,
providerSettings: z.record(z.string(), ProviderSettingSchema),
githubUser: GithubUserSchema.optional(),
githubAccessToken: SecretSchema.optional(),
vercelAccessToken: SecretSchema.optional(),
supabase: SupabaseSchema.optional(),
neon: NeonSchema.optional(),
autoApproveChanges: z.boolean().optional(),
telemetryConsent: z.enum(["opted_in", "opted_out", "unset"]).optional(),
telemetryUserId: z.string().optional(),
hasRunBefore: z.boolean().optional(),
enableDyadPro: z.boolean().optional(),
experiments: ExperimentsSchema.optional(),
lastShownReleaseNotesVersion: z.string().optional(),
maxChatTurnsInContext: z.number().optional(),
thinkingBudget: z.enum(["low", "medium", "high"]).optional(),
enableProLazyEditsMode: z.boolean().optional(),
enableProSmartFilesContextMode: z.boolean().optional(),
enableProWebSearch: z.boolean().optional(),
proSmartContextOption: z.enum(["balanced", "conservative"]).optional(),
selectedTemplateId: z.string(),
enableSupabaseWriteSqlMigration: z.boolean().optional(),
selectedChatMode: ChatModeSchema.optional(),
acceptedCommunityCode: z.boolean().optional(),
enableAutoFixProblems: z.boolean().optional(),
enableNativeGit: z.boolean().optional(),
enableAutoUpdate: z.boolean(),
releaseChannel: ReleaseChannelSchema,
runtimeMode2: RuntimeMode2Schema.optional(),
customNodePath: z.string().optional().nullable(),
////////////////////////////////
// E2E TESTING ONLY.
////////////////////////////////
isTestMode: z.boolean().optional(),
////////////////////////////////
// DEPRECATED.
////////////////////////////////
enableProSaverMode: z.boolean().optional(),
dyadProBudget: DyadProBudgetSchema.optional(),
runtimeMode: RuntimeModeSchema.optional(),
});
/**
* Type derived from the UserSettingsSchema
*/
export type UserSettings = z.infer<typeof UserSettingsSchema>;
export function isDyadProEnabled(settings: UserSettings): boolean {
return settings.enableDyadPro === true && hasDyadProKey(settings);
}
export function hasDyadProKey(settings: UserSettings): boolean {
return !!settings.providerSettings?.auto?.apiKey?.value;
}
// Define interfaces for the props
export interface SecurityRisk {
type: "warning" | "danger";
title: string;
description: string;
}
export interface FileChange {
name: string;
path: string;
summary: string;
type: "write" | "rename" | "delete";
isServerFunction: boolean;
}
export interface CodeProposal {
type: "code-proposal";
title: string;
securityRisks: SecurityRisk[];
filesChanged: FileChange[];
packagesAdded: string[];
sqlQueries: SqlQuery[];
}
export type SuggestedAction =
| RestartAppAction
| SummarizeInNewChatAction
| RefactorFileAction
| WriteCodeProperlyAction
| RebuildAction
| RestartAction
| RefreshAction
| KeepGoingAction;
export interface RestartAppAction {
id: "restart-app";
}
export interface SummarizeInNewChatAction {
id: "summarize-in-new-chat";
}
export interface WriteCodeProperlyAction {
id: "write-code-properly";
}
export interface RefactorFileAction {
id: "refactor-file";
path: string;
}
export interface RebuildAction {
id: "rebuild";
}
export interface RestartAction {
id: "restart";
}
export interface RefreshAction {
id: "refresh";
}
export interface KeepGoingAction {
id: "keep-going";
}
export interface ActionProposal {
type: "action-proposal";
actions: SuggestedAction[];
}
export interface TipProposal {
type: "tip-proposal";
title: string;
description: string;
}
export type Proposal = CodeProposal | ActionProposal | TipProposal;
export interface ProposalResult {
proposal: Proposal;
chatId: number;
messageId: number;
}
export interface SqlQuery {
content: string;
description?: string;
}