import z from 'zod' import type { PackageManager } from './package-manager.js' export type StatusStepType = | 'file' | 'command' | 'info' | 'package-manager' | 'other' export const AddOnSelectOptionSchema = z.object({ type: z.literal('select'), label: z.string(), description: z.string().optional(), default: z.string(), options: z.array( z.object({ value: z.string(), label: z.string(), }), ), }) export const AddOnOptionSchema = z.discriminatedUnion('type', [ AddOnSelectOptionSchema, ]) export const AddOnOptionsSchema = z.record(z.string(), AddOnOptionSchema) export const AddOnBaseSchema = z.object({ id: z.string(), name: z.string(), description: z.string(), author: z.string().optional(), version: z.string().optional(), link: z.string().optional(), license: z.string().optional(), warning: z.string().optional(), tailwind: z.boolean().optional(), type: z.enum(['add-on', 'example', 'starter', 'toolchain', 'deployment']), supportedPackageManagers: z .array(z.enum(['npm', 'yarn', 'pnpm', 'bun', 'deno'])) .optional(), category: z .enum([ 'tanstack', 'database', 'orm', 'auth', 'deploy', 'styling', 'monitoring', 'cms', 'api', 'analytics', 'i18n', 'tooling', 'other', ]) .optional(), exclusive: z .array(z.enum(['orm', 'auth', 'deploy', 'database', 'linter'])) .optional(), color: z.string().optional(), priority: z.number().optional(), partner: z .object({ id: z.string(), tier: z.enum(['gold', 'silver', 'bronze']), placementWeight: z.number().positive().optional(), }) .optional(), command: z .object({ command: z.string(), args: z.array(z.string()).optional(), }) .optional(), routes: z .array( z.object({ url: z.string().optional(), name: z.string().optional(), path: z.string().optional(), jsName: z.string().optional(), icon: z.string().optional(), }), ) .optional(), packageAdditions: z .object({ dependencies: z.record(z.string(), z.string()).optional(), devDependencies: z.record(z.string(), z.string()).optional(), engines: z.record(z.string(), z.string()).optional(), pnpm: z .object({ onlyBuiltDependencies: z.array(z.string()).optional(), }) .optional(), scripts: z.record(z.string(), z.string()).optional(), }) .optional(), variables: z.array(z.unknown()).optional(), shadcnComponents: z.array(z.string()).optional(), dependsOn: z.array(z.string()).optional(), smallLogo: z.string().optional(), logo: z.string().optional(), addOnSpecialSteps: z.array(z.string()).optional(), createSpecialSteps: z.array(z.string()).optional(), postInitSpecialSteps: z.array(z.string()).optional(), options: AddOnOptionsSchema.optional(), envVars: z .array( z.object({ name: z.string(), description: z.string().optional(), required: z.boolean().optional(), default: z.string().optional(), secret: z.boolean().optional(), file: z.enum(['.env', '.env.local']).optional(), }), ) .optional(), default: z.boolean().optional(), }) export const StarterSchema = AddOnBaseSchema.extend({ framework: z.string(), mode: z.string(), typescript: z.boolean(), banner: z.string().optional(), }) export const StarterCompiledSchema = StarterSchema.extend({ files: z.record(z.string(), z.string()), deletedFiles: z.array(z.string()), }) export const IntegrationSchema = z.object({ type: z.string().optional(), path: z.string().optional(), jsName: z.string().optional(), import: z.string().optional(), code: z.string().optional(), }) export const AddOnInfoSchema = AddOnBaseSchema.extend({ modes: z.array(z.string()), integrations: z.array(IntegrationSchema).optional(), phase: z.enum(['setup', 'add-on', 'example']), readme: z.string().optional(), readmeIsEjs: z.boolean().optional(), }) export const AddOnCompiledSchema = AddOnInfoSchema.extend({ files: z.record(z.string(), z.string()), deletedFiles: z.array(z.string()), packageTemplate: z.string().optional(), }) export type AddOnSelectOption = z.infer export type AddOnOption = z.infer export type AddOnOptions = z.infer export type Integration = z.infer export type AddOnBase = z.infer export type StarterInfo = z.infer export type StarterCompiled = z.infer export type AddOnInfo = z.infer export type AddOnCompiled = z.infer export interface AddOnSelection { id: string enabled: boolean options: Record } export type FileBundleHandler = { getFiles: () => Promise> getFileContents: (path: string) => Promise getDeletedFiles: () => Promise> } export type AddOn = AddOnCompiled & FileBundleHandler export type Starter = StarterCompiled & FileBundleHandler export type FrameworkDefinition = { id: string name: string description: string version: string base: Record addOns: Array basePackageJSON: Record optionalPackages: Record supportedModes: Record< string, { displayName: string description: string forceTypescript: boolean } > } export type Framework = Omit & FileBundleHandler & { getAddOns: () => Array } export interface Options { projectName: string targetDir: string framework: Framework mode: string typescript: boolean tailwind: boolean packageManager: PackageManager git: boolean install?: boolean intent: boolean chosenAddOns: Array addOnOptions: Record> starter?: Starter | undefined projectPreset?: 'default' | 'blank' routerOnly?: boolean includeExamples?: boolean envVarValues?: Record } export type SerializedOptions = Omit< Options, 'chosenAddOns' | 'starter' | 'framework' > & { chosenAddOns: Array starter?: string | undefined framework: string } type ProjectEnvironment = { startRun: () => void finishRun: () => void getErrors: () => Array } type FileEnvironment = { appendFile: (path: string, contents: string) => Promise copyFile: (from: string, to: string) => Promise writeFile: (path: string, contents: string) => Promise writeFileBase64: (path: string, base64Contents: string) => Promise execute: ( command: string, args: Array, cwd: string, options?: { inherit?: boolean }, ) => Promise<{ stdout: string }> deleteFile: (path: string) => Promise exists: (path: string) => boolean isDirectory: (path: string) => boolean readFile: (path: string) => Promise readdir: (path: string) => Promise> rimraf: (path: string) => Promise } export type StatusEvent = { id: string type: StatusStepType message: string } export type StopEvent = { id: string } type UIEnvironment = { appName: string startStep: (info: { id: string type: StatusStepType message: string }) => void finishStep: (id: string, finalMessage: string) => void intro: (message: string) => void outro: (message: string) => void info: (title?: string, message?: string) => void error: (title?: string, message?: string) => void warn: (title?: string, message?: string) => void spinner: () => { start: (message: string) => void stop: (message: string) => void } confirm: (message: string) => Promise } export type Environment = ProjectEnvironment & FileEnvironment & UIEnvironment // Attribution tracking types for file provenance export interface LineAttribution { line: number sourceId: string sourceName: string type: 'original' | 'injected' } export interface FileProvenance { source: 'framework' | 'add-on' | 'starter' sourceId: string sourceName: string } export interface AttributedFile { content: string provenance: FileProvenance lineAttributions: Array } export interface DependencyAttribution { name: string version: string type: 'dependency' | 'devDependency' sourceId: string sourceName: string } // Integration with source add-on tracking (used in templates and attribution) export type IntegrationWithSource = Integration & { _sourceId: string _sourceName: string }