import { renderForOptions } from './edge-render.js' import { formatCommand, sortObject } from './utils.js' import { getPackageManagerExecuteCommand } from './package-manager.js' import type { Options } from './types.js' export function mergePackageJSON( packageJSON: Record, overlayPackageJSON?: Record, ) { const packageDependencies = packageJSON.dependencies && typeof packageJSON.dependencies === 'object' ? (packageJSON.dependencies as Record) : {} const overlayDependencies = overlayPackageJSON?.dependencies && typeof overlayPackageJSON.dependencies === 'object' ? (overlayPackageJSON.dependencies as Record) : {} const packageDevDependencies = packageJSON.devDependencies && typeof packageJSON.devDependencies === 'object' ? (packageJSON.devDependencies as Record) : {} const overlayDevDependencies = overlayPackageJSON?.devDependencies && typeof overlayPackageJSON.devDependencies === 'object' ? (overlayPackageJSON.devDependencies as Record) : {} const packageScripts = packageJSON.scripts && typeof packageJSON.scripts === 'object' ? (packageJSON.scripts as Record) : {} const overlayScripts = overlayPackageJSON?.scripts && typeof overlayPackageJSON.scripts === 'object' ? (overlayPackageJSON.scripts as Record) : {} const mergedPackageJSON: Record = { ...packageJSON, ...(overlayPackageJSON || {}), dependencies: { ...packageDependencies, ...overlayDependencies, }, devDependencies: { ...packageDevDependencies, ...overlayDevDependencies, }, scripts: { ...packageScripts, ...overlayScripts, }, } const packagePnpm = packageJSON.pnpm && typeof packageJSON.pnpm === 'object' ? (packageJSON.pnpm as Record) : undefined const overlayPnpm = overlayPackageJSON?.pnpm && typeof overlayPackageJSON.pnpm === 'object' ? (overlayPackageJSON.pnpm as Record) : undefined const baseOnlyBuiltDependencies = Array.isArray( packagePnpm?.onlyBuiltDependencies, ) ? packagePnpm.onlyBuiltDependencies : [] const overlayOnlyBuiltDependencies = Array.isArray( overlayPnpm?.onlyBuiltDependencies, ) ? overlayPnpm.onlyBuiltDependencies : [] const onlyBuiltDependencies = [ ...new Set([ ...baseOnlyBuiltDependencies, ...overlayOnlyBuiltDependencies, ]), ] if (packagePnpm || overlayPnpm) { mergedPackageJSON.pnpm = { ...packagePnpm, ...overlayPnpm, } if (onlyBuiltDependencies.length) { const mergedPnpm = mergedPackageJSON.pnpm as Record mergedPnpm.onlyBuiltDependencies = onlyBuiltDependencies } } return mergedPackageJSON } export function createPackageJSON(options: Options) { const packageManager = options.packageManager const blank = options.projectPreset === 'blank' const includeDevtools = !blank || options.chosenAddOns.some((addOn) => addOn.integrations?.some( (integration) => integration.type === 'devtools', ), ) function getPackageManagerExecuteScript( pkg: string, args: Array = [], ) { return formatCommand(getPackageManagerExecuteCommand(packageManager, pkg, args)) } let packageJSON: Record = { ...(JSON.parse( JSON.stringify(options.framework.basePackageJSON), ) as Record), name: options.projectName, } const additions: Array | undefined> = [ options.framework.optionalPackages.typescript, includeDevtools ? options.framework.optionalPackages.devtools : undefined, !blank && options.includeExamples !== false ? options.framework.optionalPackages.examples : undefined, options.tailwind ? options.framework.optionalPackages.tailwindcss : undefined, options.mode ? options.framework.optionalPackages[options.mode] : undefined, ] for (const addition of additions.filter( (addition): addition is Record => Boolean(addition), )) { packageJSON = mergePackageJSON(packageJSON, addition) } for (const addOn of options.chosenAddOns) { let addOnPackageJSON = addOn.packageAdditions as | Record | undefined if (addOn.packageTemplate) { const templateValues = { packageManager: options.packageManager, projectName: options.projectName, typescript: true, tailwind: options.tailwind, blank, js: 'ts', jsx: 'tsx', fileRouter: options.mode === 'file-router', codeRouter: options.mode === 'code-router', routerOnly: options.routerOnly === true, addOnEnabled: options.chosenAddOns.reduce>( (acc, addon) => { acc[addon.id] = true return acc }, {}, ), addOnOption: options.addOnOptions, addOns: options.chosenAddOns, getPackageManagerExecuteScript, } try { addOnPackageJSON = JSON.parse( renderForOptions(options, addOn.packageTemplate, templateValues), ) } catch (error) { console.error( `Error processing package.json.ejs for add-on ${addOn.id}:`, error, ) } } packageJSON = mergePackageJSON(packageJSON, addOnPackageJSON) } if (options.starter) { packageJSON = mergePackageJSON( packageJSON, options.starter.packageAdditions as Record | undefined, ) } const dependencies = packageJSON.dependencies as | Record | undefined const devDependencies = packageJSON.devDependencies as | Record | undefined const scripts = packageJSON.scripts as Record | undefined if (options.routerOnly) { if (options.framework.id === 'react') { delete dependencies?.['@tanstack/react-start'] delete dependencies?.['@tanstack/react-router-ssr-query'] packageJSON.devDependencies = { ...(devDependencies ?? {}), '@tanstack/router-plugin': devDependencies?.['@tanstack/router-plugin'] ?? 'latest', } } if (options.framework.id === 'solid') { delete dependencies?.['@tanstack/solid-start'] delete dependencies?.['@tanstack/solid-router-ssr-query'] delete scripts?.start packageJSON.devDependencies = { ...(devDependencies ?? {}), '@tanstack/router-plugin': devDependencies?.['@tanstack/router-plugin'] ?? 'latest', } } } packageJSON.dependencies = sortObject( (packageJSON.dependencies ?? {}) as Record, ) packageJSON.devDependencies = sortObject( (packageJSON.devDependencies ?? {}) as Record, ) return packageJSON }