From 594d0c530789262a449c67841e3a1c1ea8cb3758 Mon Sep 17 00:00:00 2001 From: Tanner Linsley Date: Fri, 17 Jul 2026 11:00:53 -0600 Subject: [PATCH 1/4] feat: add blank project preset --- .agents/index.md | 8 +- .changeset/clean-cats-start.md | 12 + README.md | 3 + docs/cli-reference.md | 15 +- docs/quick-start.md | 24 +- examples/react/blog/package.json | 1 - .../cli/skills/create-app-scaffold/SKILL.md | 45 ++- .../create-flag-compatibility-matrix.md | 8 +- .../references/framework-adapters.md | 2 +- packages/cli/src/cli.ts | 32 +- packages/cli/src/command-line.ts | 46 ++- packages/cli/src/options.ts | 28 +- packages/cli/src/types.ts | 1 + packages/cli/tests-e2e/blank-smoke.spec.ts | 195 ++++++++++++ packages/cli/tests-e2e/helpers.ts | 16 + packages/cli/tests/command-line.test.ts | 76 ++++- packages/cli/tests/options.test.ts | 19 ++ packages/create/package.json | 2 +- packages/create/scripts/copy-assets.mjs | 35 +++ packages/create/scripts/generate-manifest.mjs | 2 + packages/create/src/add-to-app.ts | 13 +- packages/create/src/create-app.ts | 12 +- packages/create/src/edge-create-app.ts | 12 +- packages/create/src/edge-package-json.ts | 19 +- packages/create/src/edge-render.ts | 2 + packages/create/src/edge-template-file.ts | 6 +- .../react/add-ons/posthog/info.json | 1 - .../frameworks/react/add-ons/shadcn/info.json | 1 + .../react/add-ons/shadcn/package.json | 3 + .../react/add-ons/shopify/info.json | 1 + .../react/add-ons/strapi/package.json | 1 + .../react/add-ons/tanstack-query/package.json | 3 +- .../react/examples/events/package.json | 1 + .../react/examples/resume/package.json | 1 + .../react/hosts/cloudflare/package.json.ejs | 4 +- .../react/project/base/README.md.ejs | 37 ++- .../react/project/base/package.json | 18 +- .../react/project/base/public/favicon.ico | Bin 3870 -> 0 bytes .../react/project/base/public/logo192.png | Bin 5347 -> 0 bytes .../react/project/base/public/logo512.png | Bin 9664 -> 0 bytes .../react/project/base/public/manifest.json | 25 -- .../react/project/base/public/robots.txt | 3 - .../react/project/base/src/App.test.tsx.ejs | 10 - .../project/base/src/routes/__root.tsx.ejs | 16 +- .../project/base/src/routes/index.tsx.ejs | 17 +- .../react/project/base/src/styles.css.ejs | 19 +- .../react/project/base/vite.config.ts.ejs | 9 +- .../frameworks/react/project/packages.json | 20 +- .../solid/add-ons/solid-ui/info.json | 1 + .../solid/project/base/README.md.ejs | 29 +- .../project/base/_dot_cursorrules.append | 35 --- .../solid/project/base/package.json | 17 +- .../solid/project/base/public/favicon.ico | Bin 3870 -> 0 bytes .../solid/project/base/public/logo192.png | Bin 5347 -> 0 bytes .../solid/project/base/public/logo512.png | Bin 9664 -> 0 bytes .../solid/project/base/public/manifest.json | 25 -- .../solid/project/base/public/robots.txt | 3 - .../solid/project/base/src/App.test.tsx.ejs | 10 - .../project/base/src/routes/__root.tsx.ejs | 11 +- .../project/base/src/routes/index.tsx.ejs | 17 +- .../solid/project/base/src/styles.css.ejs | 19 +- .../solid/project/base/vite.config.ts.ejs | 9 +- .../frameworks/solid/project/packages.json | 11 +- packages/create/src/package-json.ts | 19 +- packages/create/src/template-file.ts | 6 +- packages/create/src/types.ts | 1 + packages/create/tests/add-to-app.test.ts | 31 ++ packages/create/tests/blank-preset.test.ts | 287 ++++++++++++++++++ .../tests/react-template-dependencies.test.ts | 68 +++++ 69 files changed, 1180 insertions(+), 243 deletions(-) create mode 100644 .changeset/clean-cats-start.md create mode 100644 packages/cli/tests-e2e/blank-smoke.spec.ts create mode 100644 packages/create/scripts/copy-assets.mjs delete mode 100644 packages/create/src/frameworks/react/project/base/public/favicon.ico delete mode 100644 packages/create/src/frameworks/react/project/base/public/logo192.png delete mode 100644 packages/create/src/frameworks/react/project/base/public/logo512.png delete mode 100644 packages/create/src/frameworks/react/project/base/public/manifest.json delete mode 100644 packages/create/src/frameworks/react/project/base/public/robots.txt delete mode 100644 packages/create/src/frameworks/react/project/base/src/App.test.tsx.ejs delete mode 100644 packages/create/src/frameworks/solid/project/base/_dot_cursorrules.append delete mode 100644 packages/create/src/frameworks/solid/project/base/public/favicon.ico delete mode 100644 packages/create/src/frameworks/solid/project/base/public/logo192.png delete mode 100644 packages/create/src/frameworks/solid/project/base/public/logo512.png delete mode 100644 packages/create/src/frameworks/solid/project/base/public/manifest.json delete mode 100644 packages/create/src/frameworks/solid/project/base/public/robots.txt delete mode 100644 packages/create/src/frameworks/solid/project/base/src/App.test.tsx.ejs create mode 100644 packages/create/tests/blank-preset.test.ts create mode 100644 packages/create/tests/react-template-dependencies.test.ts diff --git a/.agents/index.md b/.agents/index.md index f3e7c33b..68795b27 100644 --- a/.agents/index.md +++ b/.agents/index.md @@ -14,6 +14,9 @@ npx @tanstack/cli add clerk drizzle # List available add-ons npx @tanstack/cli create --list-add-ons + +# Minimal one-route Start app +npx @tanstack/cli create my-app --blank -y ``` ## Monorepo Structure @@ -70,6 +73,7 @@ node ../cli/packages/cli/dist/index.js create my-app | `--toolchain ` | Toolchain (use `--list-add-ons` to see options) | | `--deployment ` | Deployment target (use `--list-add-ons` to see options) | | `--template ` | Use template URL/path or built-in ID | +| `--blank` | Minimal project without starter UI or default tooling | | `--no-git` | Skip git init | | `--no-install` | Skip npm install | | `-y` | Accept defaults | @@ -81,7 +85,8 @@ node ../cli/packages/cli/dist/index.js create my-app | ---------------- | ------- | ---------------------------------------- | | `projectName` | string | Project name | | `typescript` | boolean | Always true (TanStack Start requires TS) | -| `tailwind` | boolean | Always true (Tailwind always enabled) | +| `tailwind` | boolean | False for blank unless an add-on requires it | +| `blank` | boolean | True when using the blank project preset | | `fileRouter` | boolean | Always true | | `addOnEnabled` | object | `{ [id]: boolean }` | | `addOnOption` | object | `{ [id]: options }` | @@ -138,4 +143,3 @@ skills: load: "packages/cli/skills/maintain-custom-addons-dev-watch/SKILL.md" - diff --git a/.changeset/clean-cats-start.md b/.changeset/clean-cats-start.md new file mode 100644 index 00000000..3bf558df --- /dev/null +++ b/.changeset/clean-cats-start.md @@ -0,0 +1,12 @@ +--- +'@tanstack/cli': minor +'@tanstack/create': minor +--- + +Add a `--blank` project preset that creates a production-ready one-route app +without starter UI, Tailwind, devtools, test tooling, Intent setup, or unused +public assets. Pass `--intent` to opt local coding-agent skill mappings back in. +Also move integration-specific dependencies to their owning add-ons and stop +shipping an unused test stack in standard Start projects. Explicit styling and +deployment add-ons remain composable with the blank preset, including when +added later. diff --git a/README.md b/README.md index f5f3cba8..1912ae42 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,9 @@ npx @tanstack/cli create my-app # Create TanStack Start app (recommended) npx @tanstack/cli create my-app +# Create a minimal one-route Start app without selection prompts +npx @tanstack/cli create my-app --blank -y + # Create Router-only SPA (no SSR) npx @tanstack/cli create my-app --router-only diff --git a/docs/cli-reference.md b/docs/cli-reference.md index a7ad0ee8..259ae629 100644 --- a/docs/cli-reference.md +++ b/docs/cli-reference.md @@ -15,13 +15,14 @@ tanstack create [project-name] [options] |--------|-------------| | `--add-ons ` | Comma-separated add-on IDs | | `--template ` | Template URL/path or built-in template ID | +| `--blank` | Create a minimal one-route Start project without default starter UI, examples, Tailwind, devtools, or a test stack | | `--package-manager ` | `npm`, `pnpm`, `yarn`, `bun`, `deno` | | `--framework ` | `React`, `Solid` | | `--router-only` | Create file-based Router-only app without TanStack Start (add-ons/deployment/template disabled) | | `--toolchain ` | Toolchain add-on (use `--list-add-ons` to see options) | | `--deployment ` | Deployment add-on (use `--list-add-ons` to see options) | | `--examples` / `--no-examples` | Include or exclude demo/example pages | -| `--tailwind` / `--no-tailwind` | Deprecated compatibility flags; accepted but ignored (Tailwind is always enabled) | +| `--tailwind` / `--no-tailwind` | Deprecated compatibility flags for standard projects; blank projects omit Tailwind | | `--no-git` | Skip git init | | `--no-install` | Skip dependency install | | `-y, --yes` | Use defaults, skip prompts | @@ -36,6 +37,8 @@ tanstack create [project-name] [options] ```bash # Examples tanstack create my-app -y +tanstack create my-app --blank -y +tanstack create my-app --blank --deployment cloudflare -y tanstack create my-app --add-ons clerk,drizzle,tanstack-query tanstack create my-app --router-only --toolchain eslint --no-examples tanstack create my-app --template https://example.com/template.json @@ -44,6 +47,14 @@ tanstack create --list-add-ons --framework React --json tanstack create --addon-details drizzle --framework React --json ``` +`--blank` creates the smallest useful TanStack Start project: one route and no +default starter interface, examples, Tailwind, devtools, test dependencies, or +TanStack Intent setup. Pass `--intent` when the project should include local +skill mappings for coding agents. +Explicit add-ons and deployment adapters can add their own required files and +dependencies. Add `-y` to use defaults for every remaining option; when the +target is non-empty, also pass `--force` or the command exits without writing. + ### Programmatic generation Use `@tanstack/create/worker` in Cloudflare Workers and other edge SSR runtimes. It does not import the generated template manifest at module startup. Instead, provide a loader for the framework and add-on chunks your Worker supports. @@ -295,7 +306,7 @@ Removes `^` from version ranges for TanStack packages and adds any missing peer ## Configuration -Projects include `.tanstack.json`: +Projects include `.cta.json`: ```json { diff --git a/docs/quick-start.md b/docs/quick-start.md index a0c76782..f49fb0fd 100644 --- a/docs/quick-start.md +++ b/docs/quick-start.md @@ -17,13 +17,25 @@ Interactive prompts guide you through project name, package manager, and add-on # Defaults only (TanStack Start + file-router) npx @tanstack/cli create my-app -y +# Minimal one-route Start project +npx @tanstack/cli create my-app --blank -y + +# Minimal Start project configured for Cloudflare +npx @tanstack/cli create my-app --blank --deployment cloudflare -y + # With add-ons -npx @tanstack/cli create my-app --add-ons tanstack-query,clerk,drizzle +npx @tanstack/cli create my-app --add-ons tanstack-query,clerk,drizzle -y # Router-only SPA (no SSR) -npx @tanstack/cli create my-app --router-only +npx @tanstack/cli create my-app --router-only -y ``` +Blank projects omit the default starter interface, examples, Tailwind, +devtools, test stack, and TanStack Intent setup. Pass `--intent` to include +local skill mappings for coding agents. Explicit add-ons can add their own +required files and dependencies. Pass `-y` to skip selection prompts. A +non-empty target also requires `--force`. + ## Run the Project ```bash @@ -34,7 +46,7 @@ pnpm dev ## Environment Variables -Some add-ons require API keys. After creation: +Some add-ons require API keys. When the project includes `.env.example`: ```bash cp .env.example .env @@ -49,9 +61,9 @@ my-app/ │ ├── routes/ # File-based routing │ │ ├── __root.tsx # Root layout │ │ └── index.tsx # Home page -│ └── integrations/ # Add-on integration code -├── .tanstack.json # CLI config -└── .env.example # Required env vars +│ └── integrations/ # Present when selected add-ons need it +├── .cta.json # CLI config +└── .env.example # Present when selected add-ons need env vars ``` ## Next Steps diff --git a/examples/react/blog/package.json b/examples/react/blog/package.json index 9e6f7aa9..38e16499 100644 --- a/examples/react/blog/package.json +++ b/examples/react/blog/package.json @@ -19,7 +19,6 @@ "@tanstack/react-router-ssr-query": "^1.131.7", "@tanstack/react-start": "^1.132.0", "@tanstack/router-plugin": "^1.132.0", - "lucide-react": "^0.545.0", "react": "^19.2.0", "react-dom": "^19.2.0", "tailwindcss": "^4.1.18" diff --git a/packages/cli/skills/create-app-scaffold/SKILL.md b/packages/cli/skills/create-app-scaffold/SKILL.md index 8a2955fe..30a1d330 100644 --- a/packages/cli/skills/create-app-scaffold/SKILL.md +++ b/packages/cli/skills/create-app-scaffold/SKILL.md @@ -2,12 +2,12 @@ name: create-app-scaffold description: > Scaffold a TanStack app with tanstack create using --framework, --template, - --toolchain, --deployment, --add-ons, and --router-only. Covers flag + --toolchain, --deployment, --add-ons, --blank, and --router-only. Covers flag compatibility, non-interactive defaults, and intent-preserving command construction. type: core library: tanstack-cli -library_version: "0.62.1" +library_version: "0.70.0" --- # Create App Scaffold @@ -37,6 +37,20 @@ npx @tanstack/cli create acme-solid \ -y ``` +### Start from a minimal production-ready scaffold + +Use `--blank` when the task does not need the default starter UI, examples, +Tailwind, devtools, test tooling, or local Intent setup. Explicit integrations +remain composable; add `--intent` when the generated project should contain +skill mappings for later coding-agent work. + +```bash +npx @tanstack/cli create acme-web \ + --blank \ + --deployment cloudflare \ + -y +``` + ### Use router-only mode for compatibility scaffolds only ```bash @@ -75,7 +89,7 @@ In non-interactive runs, empty add-on selection can complete with defaults and s Source: https://github.com/TanStack/cli/issues/234 -### HIGH Assume --no-tailwind is still supported +### HIGH Use --no-tailwind when a minimal scaffold is required Wrong: ```bash @@ -84,12 +98,31 @@ npx @tanstack/cli create my-app --no-tailwind -y Correct: ```bash -npx @tanstack/cli create my-app -y +npx @tanstack/cli create my-app --blank -y +``` + +`--no-tailwind` remains a deprecated compatibility flag for standard +scaffolds. `--blank` is the supported preset for a minimal project without +Tailwind. + +Source: packages/cli/src/command-line.ts:386 + +### HIGH Combine --blank with a template, examples, or --tailwind + +Wrong: +```bash +npx @tanstack/cli create my-app --blank --template ecommerce -y ``` -`--no-tailwind` is deprecated and ignored, so output still includes Tailwind and diverges from expected stack constraints. +Correct: +```bash +npx @tanstack/cli create my-app --blank --add-ons drizzle -y +``` -Source: packages/cli/src/command-line.ts:369 +Templates, examples, and `--tailwind` replace the output constraints that +`--blank` guarantees, so the CLI rejects those combinations. Deployment, +toolchain, and add-on selections remain supported; a selected styling add-on +can opt the generated app back into Tailwind when it requires it. ### CRITICAL Combine router-only with template/deployment/add-ons diff --git a/packages/cli/skills/create-app-scaffold/references/create-flag-compatibility-matrix.md b/packages/cli/skills/create-app-scaffold/references/create-flag-compatibility-matrix.md index 2a9a1aba..2b68efd0 100644 --- a/packages/cli/skills/create-app-scaffold/references/create-flag-compatibility-matrix.md +++ b/packages/cli/skills/create-app-scaffold/references/create-flag-compatibility-matrix.md @@ -1,6 +1,6 @@ # Create Flag Compatibility Matrix -Targets `@tanstack/cli` v0.62.1. +Targets `@tanstack/cli` v0.70.0. ## Compatibility @@ -12,7 +12,8 @@ Targets `@tanstack/cli` v0.62.1. | `--deployment` | yes | no | Ignored in router-only mode. | | `--template` / `--starter` | yes | no | Ignored in router-only mode. | | `--template-id` | yes | no | Ignored in router-only mode. | -| `--tailwind` / `--no-tailwind` | deprecated/ignored | deprecated/ignored | Tailwind is always enabled. | +| `--blank` | yes | yes | Omits starter UI, examples, Tailwind, devtools, and test tooling unless an explicit integration requires them. Cannot be combined with a template, `--examples`, or `--tailwind`. | +| `--tailwind` / `--no-tailwind` | deprecated/ignored | deprecated/ignored | Standard scaffolds enable Tailwind; use `--blank` for the supported minimal preset. | Source: `packages/cli/src/command-line.ts:337` @@ -29,6 +30,9 @@ Source: `packages/cli/src/command-line.ts:337` # Full scaffold preset npx @tanstack/cli create app --framework react --add-ons tanstack-query --deployment netlify -y +# Minimal Start preset +npx @tanstack/cli create app --blank --framework react --deployment cloudflare -y + # Router-only preset npx @tanstack/cli create app --router-only --framework react --toolchain biome -y ``` diff --git a/packages/cli/skills/create-app-scaffold/references/framework-adapters.md b/packages/cli/skills/create-app-scaffold/references/framework-adapters.md index c9cfc433..f932bf95 100644 --- a/packages/cli/skills/create-app-scaffold/references/framework-adapters.md +++ b/packages/cli/skills/create-app-scaffold/references/framework-adapters.md @@ -1,6 +1,6 @@ # Framework Adapter Options -Targets `@tanstack/cli` v0.61.0. +Targets `@tanstack/cli` v0.70.0. ## Supported values diff --git a/packages/cli/src/cli.ts b/packages/cli/src/cli.ts index 797561d2..e60c5e72 100644 --- a/packages/cli/src/cli.ts +++ b/packages/cli/src/cli.ts @@ -182,6 +182,7 @@ function getCreateTelemetryProperties(projectName: string, options: CliOptions) command_variant: getCreateCommandVariant(options), deployment: options.deployment ? sanitizeId(options.deployment) : undefined, examples: options.examples, + blank: !!options.blank, framework: options.framework ? sanitizeId(options.framework) : undefined, git: options.git, install: options.install !== false, @@ -221,6 +222,7 @@ function getResolvedCreateTelemetryProperties( add_on_ids: addOnIds, deployment: deployment ? sanitizeId(deployment.id) : undefined, examples: includeExamples, + blank: finalOptions.projectPreset === 'blank', framework: sanitizeId(finalOptions.framework.id), git: finalOptions.git, install: finalOptions.install !== false, @@ -264,6 +266,7 @@ export function cli({ async function confirmTargetDirectorySafety( targetDir: string, forced?: boolean, + nonInteractive?: boolean, ) { if (forced) { return @@ -281,6 +284,12 @@ export function cli({ return } + if (nonInteractive) { + throw new Error( + `Target directory "${targetDir}" already exists and is not empty. Pass --force to continue non-interactively.`, + ) + } + const shouldContinue = await confirm({ message: `Target directory "${targetDir}" already exists and is not empty. Continue anyway?`, initialValue: false, @@ -298,6 +307,9 @@ export function cli({ lines.push(` Location: ${finalOptions.targetDir}`) lines.push(` Framework: ${finalOptions.framework.name}`) lines.push(` Mode: ${finalOptions.mode}`) + if (finalOptions.projectPreset === 'blank') { + lines.push(` Preset: blank`) + } lines.push(` Package manager: ${finalOptions.packageManager}`) if (finalOptions.starter) { lines.push(` Template: ${finalOptions.starter.name}`) @@ -512,7 +524,11 @@ export function cli({ ) } const silentEnvironment = createUIEnvironment(appName, true, () => currentTelemetry) - await confirmTargetDirectorySafety(normalizedOpts.targetDir, options.force) + await confirmTargetDirectorySafety( + normalizedOpts.targetDir, + options.force, + !!options.nonInteractive || !!options.yes, + ) await createApp(silentEnvironment, normalizedOpts) console.log(chalk.gray('└─') + ' ' + chalk.green('✓') + ` app created`) @@ -895,7 +911,11 @@ export function cli({ if (cameFromPrompts) { await confirmCreateOptions(finalOptions) } - await confirmTargetDirectorySafety(finalOptions.targetDir, options.force) + await confirmTargetDirectorySafety( + finalOptions.targetDir, + options.force, + nonInteractive, + ) await createApp(environment, finalOptions) }, ) @@ -968,13 +988,17 @@ export function cli({ '--router-only', 'Use router-only compatibility mode (file-based routing without TanStack Start)', ) + .option( + '--blank', + 'Create a minimal one-route app without default starter UI, Tailwind, devtools, or tests', + ) .option( '--tailwind', - 'Deprecated: compatibility flag; Tailwind is always enabled', + 'Deprecated: compatibility flag; standard scaffolds always enable Tailwind', ) .option( '--no-tailwind', - 'Deprecated: compatibility flag; Tailwind opt-out is ignored', + 'Deprecated: compatibility flag; use --blank for a project without Tailwind', ) .option('--examples', 'include demo/example pages') .option('--no-examples', 'exclude demo/example pages') diff --git a/packages/cli/src/command-line.ts b/packages/cli/src/command-line.ts index c375e079..7d56d9e7 100644 --- a/packages/cli/src/command-line.ts +++ b/packages/cli/src/command-line.ts @@ -321,6 +321,31 @@ export function validateLegacyCreateFlags(cliOptions: CliOptions): { const warnings: Array = [] const legacyTemplate = getLegacyTemplateValue(cliOptions.template) + if ( + cliOptions.blank && + (cliOptions.starter || cliOptions.template || cliOptions.templateId) + ) { + return { + warnings, + error: + '--blank cannot be combined with --starter, --template, or --template-id.', + } + } + + if (cliOptions.blank && cliOptions.examples === true) { + return { + warnings, + error: '--blank cannot be combined with --examples.', + } + } + + if (cliOptions.blank && cliOptions.tailwind === true) { + return { + warnings, + error: '--blank cannot be combined with --tailwind.', + } + } + if (cliOptions.starter) { warnings.push( 'The --starter flag is deprecated; prefer --template instead. Backward compatibility remains for now.', @@ -361,13 +386,13 @@ export function validateLegacyCreateFlags(cliOptions: CliOptions): { if (cliOptions.tailwind === true) { warnings.push( - 'The --tailwind flag is deprecated and ignored. Tailwind is always enabled in TanStack Start scaffolds.', + 'The --tailwind flag is deprecated and ignored. Tailwind is enabled in standard TanStack Start scaffolds.', ) } - if (cliOptions.tailwind === false) { + if (cliOptions.tailwind === false && !cliOptions.blank) { warnings.push( - 'The --no-tailwind flag is deprecated and ignored. Tailwind opt-out is intentionally unsupported to keep add-on permutations maintainable; remove Tailwind after scaffolding if needed.', + 'The --no-tailwind flag is deprecated and ignored for standard scaffolds. Use --blank to create a minimal project without Tailwind.', ) } @@ -428,6 +453,7 @@ export async function normalizeOptions( // Mode is always file-router (TanStack Start) let mode = 'file-router' let routerOnly = !!cliOptions.routerOnly + const blank = cliOptions.blank === true const legacyTemplate = getLegacyTemplateValue(cliOptions.template) @@ -450,15 +476,14 @@ export async function normalizeOptions( const preferredFramework = (cliOptions.framework || 'react').toLowerCase() - const starter = !routerOnly && cliOptions.starter + const starter = !routerOnly && !blank && cliOptions.starter ? await loadStarter( await resolveStarterSpecifier(cliOptions.starter, preferredFramework), ) : undefined - // TypeScript and Tailwind are always enabled with TanStack Start + // TypeScript is always enabled with TanStack Start. const typescript = true - const tailwind = true if (starter) { cliOptions.framework = starter.framework @@ -512,11 +537,13 @@ export async function normalizeOptions( return [] } - const includeExamples = cliOptions.examples ?? !routerOnly + const includeExamples = blank ? false : (cliOptions.examples ?? !routerOnly) const chosenAddOnsRaw = await selectAddOns() const chosenAddOns = includeExamples ? chosenAddOnsRaw : chosenAddOnsRaw.filter((addOn) => addOn.type !== 'example') + const tailwind = + !blank || chosenAddOns.some((addOn) => addOn.tailwind === true) // Handle add-on configuration option let addOnOptionsFromCLI = {} @@ -529,7 +556,7 @@ export async function normalizeOptions( } } - const normalized = { + const normalized: Options = { projectName: projectName, targetDir, framework, @@ -542,13 +569,14 @@ export async function normalizeOptions( DEFAULT_PACKAGE_MANAGER, git: cliOptions.git ?? true, install: cliOptions.install, - intent: cliOptions.intent ?? true, + intent: cliOptions.intent ?? !blank, chosenAddOns, addOnOptions: { ...populateAddOnOptionsDefaults(chosenAddOns), ...addOnOptionsFromCLI, }, starter: starter, + projectPreset: blank ? 'blank' : 'default', } ;(normalized as Options & { includeExamples?: boolean }).includeExamples = diff --git a/packages/cli/src/options.ts b/packages/cli/src/options.ts index 26cd8a3d..f09b55a2 100644 --- a/packages/cli/src/options.ts +++ b/packages/cli/src/options.ts @@ -52,6 +52,7 @@ export async function promptForCreateOptions( }, ): Promise | undefined> { const options = {} as Required + const blank = cliOptions.blank === true if (cliOptions.framework) { options.framework = getFrameworkById(cliOptions.framework)! @@ -106,7 +107,7 @@ export async function promptForCreateOptions( } } - if (!routerOnly && !cliOptions.starter) { + if (!routerOnly && !blank && !cliOptions.starter) { const starterChoices = await listTemplateChoices(options.framework.id) const selectedTemplateId = await selectTemplate( starterChoices.map((choice) => ({ @@ -120,7 +121,7 @@ export async function promptForCreateOptions( } } - const starter = !routerOnly && cliOptions.starter + const starter = !routerOnly && !blank && cliOptions.starter ? await loadStarter( await resolveStarterSpecifier(cliOptions.starter, options.framework.id), ) @@ -144,10 +145,10 @@ export async function promptForCreateOptions( } // Toolchain selection - const toolchain = await selectToolchain( - options.framework, - cliOptions.toolchain, - ) + const toolchain = + blank && cliOptions.toolchain === undefined + ? undefined + : await selectToolchain(options.framework, cliOptions.toolchain) // Deployment selection let deployment: string | undefined @@ -155,6 +156,8 @@ export async function promptForCreateOptions( deployment = undefined } else if (cliOptions.deployment) { deployment = cliOptions.deployment + } else if (blank) { + deployment = forcedDeployment } else if (showDeploymentOptions) { deployment = await selectDeployment( options.framework, @@ -169,8 +172,9 @@ export async function promptForCreateOptions( const addOns: Set = new Set() // Examples/demo pages are enabled by default - const includeExamples = - cliOptions.examples ?? (routerOnly ? false : await selectExamples()) + const includeExamples = blank + ? false + : (cliOptions.examples ?? (routerOnly ? false : await selectExamples())) ;(options as Required & { includeExamples?: boolean }).includeExamples = includeExamples @@ -197,7 +201,7 @@ export async function promptForCreateOptions( } addOns.add(addOn) } - } else if (!routerOnly) { + } else if (!routerOnly && !blank) { for (const addOn of await selectAddOns( options.framework, options.mode, @@ -229,8 +233,9 @@ export async function promptForCreateOptions( ? chosenAddOns : chosenAddOns.filter((addOn) => addOn.type !== 'example') - // Tailwind is always enabled - options.tailwind = true + options.tailwind = + !blank || options.chosenAddOns.some((addOn) => addOn.tailwind === true) + options.projectPreset = blank ? 'blank' : 'default' // Prompt for add-on options in interactive mode if (Array.isArray(cliOptions.addOns)) { @@ -256,6 +261,7 @@ export async function promptForCreateOptions( options.git = cliOptions.git ?? (await selectGit()) options.install = cliOptions.install ?? (await selectInstall()) + options.intent = cliOptions.intent ?? !blank if (starter) { options.starter = starter diff --git a/packages/cli/src/types.ts b/packages/cli/src/types.ts index e736cf0b..235746ae 100644 --- a/packages/cli/src/types.ts +++ b/packages/cli/src/types.ts @@ -24,6 +24,7 @@ export interface CliOptions { addOnConfig?: string force?: boolean routerOnly?: boolean + blank?: boolean template?: string tailwind?: boolean examples?: boolean diff --git a/packages/cli/tests-e2e/blank-smoke.spec.ts b/packages/cli/tests-e2e/blank-smoke.spec.ts new file mode 100644 index 00000000..677f3d8f --- /dev/null +++ b/packages/cli/tests-e2e/blank-smoke.spec.ts @@ -0,0 +1,195 @@ +import { execFile } from 'node:child_process' +import { + access, + mkdir, + mkdtemp, + readFile, + readdir, + rm, + writeFile, +} from 'node:fs/promises' +import { tmpdir } from 'node:os' +import { join, relative } from 'node:path' +import { promisify } from 'node:util' + +import { expect, test } from '@playwright/test' + +import { + attachRuntimeGuards, + createReactAppFixture, + getRepoPath, + optimizePageForFastE2E, +} from './helpers' + +const execFileAsync = promisify(execFile) + +const expectedDependencies = [ + '@tanstack/react-router', + '@tanstack/react-start', + 'react', + 'react-dom', +] + +const expectedDevDependencies = [ + '@cloudflare/vite-plugin', + '@tanstack/router-cli', + '@types/node', + '@types/react', + '@types/react-dom', + '@vitejs/plugin-react', + 'typescript', + 'vite', + 'wrangler', +] + +async function listFiles( + root: string, + directory = root, +): Promise> { + const files: Array = [] + + for (const entry of await readdir(directory, { withFileTypes: true })) { + const path = join(directory, entry.name) + if (entry.isDirectory()) { + files.push(...(await listFiles(root, path))) + } else { + files.push(relative(root, path).replaceAll('\\', '/')) + } + } + + return files.sort() +} + +test('@blocking refuses a non-empty target in non-interactive mode', async () => { + const rootDir = await mkdtemp(join(tmpdir(), 'tanstack-blank-collision-')) + const targetDir = join(rootDir, 'existing-app') + const sentinelPath = join(targetDir, 'keep.txt') + + try { + await mkdir(targetDir) + await writeFile(sentinelPath, 'keep me') + + let failure: unknown + try { + await execFileAsync( + process.execPath, + [ + getRepoPath('packages/cli/dist/index.js'), + 'create', + 'existing-app', + '--blank', + '-y', + '--no-install', + '--no-git', + ], + { cwd: rootDir, encoding: 'utf8' }, + ) + } catch (error) { + failure = error + } + + const commandError = failure as + | (Error & { code?: number; stdout?: string; stderr?: string }) + | undefined + expect(commandError?.code).toBe(1) + expect( + `${commandError?.stdout ?? ''}\n${commandError?.stderr ?? ''}`, + ).toContain('Pass --force to continue non-interactively') + await expect(readFile(sentinelPath, 'utf8')).resolves.toBe('keep me') + } finally { + await rm(rootDir, { recursive: true, force: true }) + } +}) + +test('@blocking creates a production-valid blank React app', async ({ + page, +}) => { + const fixture = await createReactAppFixture({ + appName: 'react-blank-smoke-app', + blank: true, + deployment: 'cloudflare', + packageManager: 'npm', + runQualityGatesChecks: true, + afterCreate: async (appDir) => { + await access(join(appDir, 'package-lock.json')) + await access(join(appDir, 'node_modules')) + await access(join(appDir, 'wrangler.jsonc')) + + await expect( + readFile(join(appDir, 'README.md'), 'utf8'), + ).resolves.toContain('Deploy to Cloudflare Workers') + + const routeTreePath = join(appDir, 'src/routeTree.gen.ts') + await access(routeTreePath) + await expect(readFile(routeTreePath, 'utf8')).resolves.toContain( + "from './routes/index'", + ) + + expect(await listFiles(join(appDir, 'src'))).toEqual([ + 'routeTree.gen.ts', + 'router.tsx', + 'routes/__root.tsx', + 'routes/index.tsx', + 'styles.css', + ]) + + const packageJSON = JSON.parse( + await readFile(join(appDir, 'package.json'), 'utf8'), + ) as { + scripts?: Record + dependencies?: Record + devDependencies?: Record + } + + expect(Object.keys(packageJSON.dependencies ?? {}).sort()).toEqual( + expectedDependencies, + ) + expect(Object.keys(packageJSON.devDependencies ?? {}).sort()).toEqual( + expectedDevDependencies, + ) + expect(packageJSON.scripts).toEqual({ + dev: 'vite dev --port 3000', + 'generate-routes': 'tsr generate', + build: 'vite build', + preview: 'vite preview', + deploy: 'npm run build && wrangler deploy', + }) + + for (const excludedPath of [ + 'index.html', + 'public', + 'src/App.test.tsx', + 'src/components', + 'src/main.tsx', + 'src/routes/about.tsx', + ]) { + await expect(access(join(appDir, excludedPath))).rejects.toThrow() + } + }, + }) + const guards = attachRuntimeGuards(page, fixture.url) + + try { + const response = await fetch(fixture.url) + expect(response.status).toBe(200) + expect(response.headers.get('content-type')).toContain('text/html') + + const html = await response.text() + expect(html).toContain('Welcome to TanStack Start') + expect(html).toContain('src/routes/index.tsx') + + await optimizePageForFastE2E(page) + await page.goto(fixture.url) + await expect( + page.getByRole('heading', { name: 'Welcome to TanStack Start' }), + ).toBeVisible() + } finally { + try { + guards.assertClean() + } finally { + guards.dispose() + await fixture.stop() + await fixture.cleanup() + } + } +}) diff --git a/packages/cli/tests-e2e/helpers.ts b/packages/cli/tests-e2e/helpers.ts index f85ac0c5..f5883b9b 100644 --- a/packages/cli/tests-e2e/helpers.ts +++ b/packages/cli/tests-e2e/helpers.ts @@ -23,10 +23,13 @@ type CreateAppFixtureOptions = { appName: string framework?: 'react' | 'solid' packageManager?: 'pnpm' | 'npm' | 'yarn' | 'bun' | 'deno' + blank?: boolean + deployment?: string routerOnly?: boolean template?: string addOns?: Array postCreateAddOns?: Array + afterCreate?: (appDir: string) => Promise skipDevServer?: boolean runQualityGatesChecks?: boolean } @@ -349,8 +352,11 @@ export async function createAppFixture( const rootDir = await mkdtemp(join(tmpdir(), 'tanstack-cli-e2e-')) const { appName, + blank = false, + deployment, template, addOns, + afterCreate, postCreateAddOns, skipDevServer, runQualityGatesChecks = false, @@ -375,6 +381,14 @@ export async function createAppFixture( createArgs.push('--router-only') } + if (blank) { + createArgs.push('--blank', '--yes', '--no-intent') + } + + if (deployment) { + createArgs.push('--deployment', deployment) + } + if (template) { createArgs.push('--template', template) } @@ -396,6 +410,8 @@ export async function createAppFixture( ) mark('create', createStartedAt) + await afterCreate?.(appDir) + await patchViteConfigForE2E(appDir) if (postCreateAddOns?.length) { diff --git a/packages/cli/tests/command-line.test.ts b/packages/cli/tests/command-line.test.ts index 4842b7f3..e859b8bc 100644 --- a/packages/cli/tests/command-line.test.ts +++ b/packages/cli/tests/command-line.test.ts @@ -588,6 +588,61 @@ describe('normalizeOptions', () => { expect((options as any)?.includeExamples).toBe(false) }) + it('normalizes --blank to the blank project preset', async () => { + const options = await normalizeOptions({ + projectName: 'test', + blank: true, + }) + + expect(options?.projectPreset).toBe('blank') + expect(options?.includeExamples).toBe(false) + expect(options?.tailwind).toBe(false) + expect(options?.intent).toBe(false) + }) + + it('keeps explicitly selected integrations in the blank preset', async () => { + __testRegisterFramework({ + id: 'react', + name: 'react', + getAddOns: () => [ + { + id: 'biome', + name: 'Biome', + modes: ['file-router'], + type: 'toolchain', + }, + { + id: 'cloudflare', + name: 'Cloudflare', + modes: ['file-router'], + type: 'deployment', + }, + { + id: 'tanstack-query', + name: 'TanStack Query', + modes: ['file-router'], + type: 'add-on', + }, + ], + }) + + const options = await normalizeOptions({ + projectName: 'test', + framework: 'react', + blank: true, + toolchain: 'biome', + deployment: 'cloudflare', + addOns: ['tanstack-query'], + }) + + expect(options?.projectPreset).toBe('blank') + expect(options?.chosenAddOns.map((addOn) => addOn.id).sort()).toEqual([ + 'biome', + 'cloudflare', + 'tanstack-query', + ]) + }) + it('should ignore add-ons and deployment in router-only mode but keep toolchain', async () => { __testRegisterFramework({ id: 'react', @@ -668,6 +723,25 @@ describe('validateLegacyCreateFlags', () => { expect(result.error).toBeUndefined() }) + it('accepts --blank by itself', () => { + const result = validateLegacyCreateFlags({ blank: true }) + expect(result.warnings).toEqual([]) + expect(result.error).toBeUndefined() + }) + + it.each([ + [{ starter: 'ecommerce' }, '--starter'], + [{ template: 'ecommerce' }, '--template'], + [{ templateId: 'ecommerce' }, '--template-id'], + [{ examples: true }, '--examples'], + [{ tailwind: true }, '--tailwind'], + ])('rejects --blank with conflicting flags containing %s', (flags, name) => { + const result = validateLegacyCreateFlags({ blank: true, ...flags }) + + expect(result.error).toContain('--blank') + expect(result.error).toContain(name) + }) + it('warns when --router-only is used', () => { const result = validateLegacyCreateFlags({ routerOnly: true }) expect(result.error).toBeUndefined() @@ -684,7 +758,7 @@ describe('validateLegacyCreateFlags', () => { const result = validateLegacyCreateFlags({ tailwind: false }) expect(result.error).toBeUndefined() expect(result.warnings[0]).toContain('--no-tailwind') - expect(result.warnings[0]).toContain('intentionally unsupported') + expect(result.warnings[0]).toContain('--blank') }) it('errors for JavaScript templates', () => { diff --git a/packages/cli/tests/options.test.ts b/packages/cli/tests/options.test.ts index f6fb8fa0..826af6dd 100644 --- a/packages/cli/tests/options.test.ts +++ b/packages/cli/tests/options.test.ts @@ -205,6 +205,25 @@ describe('promptForCreateOptions', () => { expect(prompts.selectTemplate).not.toHaveBeenCalled() }) + it('uses the blank preset without prompting for optional scaffold features', async () => { + setBasicSpies() + + const options = await promptForCreateOptions( + { ...baseCliOptions, blank: true }, + {}, + ) + + expect(options?.projectPreset).toBe('blank') + expect(options?.includeExamples).toBe(false) + expect(options?.tailwind).toBe(false) + expect(options?.intent).toBe(false) + expect(prompts.selectTemplate).not.toHaveBeenCalled() + expect(prompts.selectExamples).not.toHaveBeenCalled() + expect(prompts.selectToolchain).not.toHaveBeenCalled() + expect(prompts.selectDeployment).not.toHaveBeenCalled() + expect(prompts.selectAddOns).not.toHaveBeenCalled() + }) + //// Package manager it('uses the package manager from the cli options', async () => { diff --git a/packages/create/package.json b/packages/create/package.json index 8ce84896..01ecd0dc 100644 --- a/packages/create/package.json +++ b/packages/create/package.json @@ -42,7 +42,7 @@ "scripts": { "build": "npm run generate-manifest && tsc && npm run copy-assets", "generate-manifest": "node ./scripts/generate-manifest.mjs", - "copy-assets": "node -e \"const fs=require('fs');const path=require('path');function copyDir(src,dest){if(!fs.existsSync(dest))fs.mkdirSync(dest,{recursive:true});for(const entry of fs.readdirSync(src,{withFileTypes:true})){const srcPath=path.join(src,entry.name);const destPath=path.join(dest,entry.name);if(entry.isDirectory())copyDir(srcPath,destPath);else fs.copyFileSync(srcPath,destPath)}}['react','solid'].forEach(fw=>{['add-ons','toolchains','hosts','examples','project'].forEach(dir=>{const src='src/frameworks/'+fw+'/'+dir;const dest='dist/frameworks/'+fw+'/'+dir;if(fs.existsSync(src))copyDir(src,dest)})})\"", + "copy-assets": "node ./scripts/copy-assets.mjs", "dev": "npm run generate-manifest && tsc --watch", "test": "npm run generate-manifest && eslint ./src && vitest run", "test:watch": "npm run generate-manifest && vitest", diff --git a/packages/create/scripts/copy-assets.mjs b/packages/create/scripts/copy-assets.mjs new file mode 100644 index 00000000..bbcbd6bb --- /dev/null +++ b/packages/create/scripts/copy-assets.mjs @@ -0,0 +1,35 @@ +import { cpSync, existsSync, rmSync } from 'node:fs' +import { dirname, resolve } from 'node:path' +import { fileURLToPath } from 'node:url' + +const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..') +const frameworks = ['react', 'solid'] +const assetDirectories = [ + 'add-ons', + 'toolchains', + 'hosts', + 'examples', + 'project', +] + +for (const framework of frameworks) { + for (const directory of assetDirectories) { + const source = resolve( + packageRoot, + 'src/frameworks', + framework, + directory, + ) + const destination = resolve( + packageRoot, + 'dist/frameworks', + framework, + directory, + ) + + rmSync(destination, { recursive: true, force: true }) + if (existsSync(source)) { + cpSync(source, destination, { recursive: true }) + } + } +} diff --git a/packages/create/scripts/generate-manifest.mjs b/packages/create/scripts/generate-manifest.mjs index de87ed0d..fdba7e85 100644 --- a/packages/create/scripts/generate-manifest.mjs +++ b/packages/create/scripts/generate-manifest.mjs @@ -266,6 +266,7 @@ function createTemplateRendererSource(renderers = templateRenderers) { projectName, typescript, tailwind, + blank, js, jsx, fileRouter, @@ -317,6 +318,7 @@ type TemplateRenderContext = { projectName: any typescript: any tailwind: any + blank: any js: any jsx: any fileRouter: any diff --git a/packages/create/src/add-to-app.ts b/packages/create/src/add-to-app.ts index af4e224a..8349160e 100644 --- a/packages/create/src/add-to-app.ts +++ b/packages/create/src/add-to-app.ts @@ -43,16 +43,19 @@ async function createOptions( const framework = getFrameworkById(json.framework) const starter = json.starter ? await loadStarter(json.starter) : undefined + const chosenAddOns = await finalizeAddOns(framework!, json.mode!, [ + ...json.chosenAddOns, + ...addOns, + ]) return { ...json, framework, - tailwind: true, + tailwind: + (json.tailwind ?? true) || + chosenAddOns.some((addOn) => addOn.tailwind === true), addOns: true, - chosenAddOns: await finalizeAddOns(framework!, json.mode!, [ - ...json.chosenAddOns, - ...addOns, - ]), + chosenAddOns, targetDir, starter, intent: json.intent ?? false, diff --git a/packages/create/src/create-app.ts b/packages/create/src/create-app.ts index 46add011..acb4f970 100644 --- a/packages/create/src/create-app.ts +++ b/packages/create/src/create-app.ts @@ -19,7 +19,10 @@ import { runSpecialSteps } from './special-steps/index.js' import type { Environment, FileBundleHandler, Options } from './types.js' function stripExamplesFromOptions(options: Options): Options { - if (options.includeExamples !== false) { + const includeExamples = + options.projectPreset !== 'blank' && options.includeExamples !== false + + if (includeExamples) { return options } @@ -53,6 +56,7 @@ function stripExamplesFromOptions(options: Options): Options { return { ...options, + includeExamples: false, chosenAddOns, } } @@ -451,6 +455,10 @@ ${environment.getErrors().join('\n')}` ` const nextSteps = buildNextSteps(options) + const readmeDescription = + options.projectPreset === 'blank' + ? 'running, building, adding routes, and selected integrations.' + : 'testing, styling, adding routes, etc.' // Use the force luke! :) environment.outro( @@ -461,7 +469,7 @@ ${cdInstruction}% ${formatCommand( getPackageManagerScriptCommand(options.packageManager, ['dev']), )} ${nextSteps} -Please read the README.md file for information on testing, styling, adding routes, etc.${errorStatement}`, +Please read the README.md file for information on ${readmeDescription}${errorStatement}`, ) } diff --git a/packages/create/src/edge-create-app.ts b/packages/create/src/edge-create-app.ts index 77d3d65d..624ccdfd 100644 --- a/packages/create/src/edge-create-app.ts +++ b/packages/create/src/edge-create-app.ts @@ -15,7 +15,10 @@ import { import type { Environment, FileBundleHandler, Options } from './types.js' function stripExamplesFromOptions(options: Options): Options { - if (options.includeExamples !== false) { + const includeExamples = + options.projectPreset !== 'blank' && options.includeExamples !== false + + if (includeExamples) { return options } @@ -49,6 +52,7 @@ function stripExamplesFromOptions(options: Options): Options { return { ...options, + includeExamples: false, chosenAddOns, } } @@ -567,6 +571,10 @@ ${environment.getErrors().join('\n')}` ` const nextSteps = buildNextSteps(options) + const readmeDescription = + options.projectPreset === 'blank' + ? 'running, building, adding routes, and selected integrations.' + : 'testing, styling, adding routes, etc.' environment.outro( `${locationMessage} @@ -576,7 +584,7 @@ ${cdInstruction}% ${formatCommand( getPackageManagerScriptCommand(options.packageManager, ['dev']), )} ${nextSteps} -Please read the README.md file for information on testing, styling, adding routes, etc.${errorStatement}`, +Please read the README.md file for information on ${readmeDescription}${errorStatement}`, ) } diff --git a/packages/create/src/edge-package-json.ts b/packages/create/src/edge-package-json.ts index bdc9704a..56174e0c 100644 --- a/packages/create/src/edge-package-json.ts +++ b/packages/create/src/edge-package-json.ts @@ -96,6 +96,14 @@ export function mergePackageJSON( 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, @@ -113,7 +121,13 @@ export function createPackageJSON(options: Options) { const additions: Array | undefined> = [ options.framework.optionalPackages.typescript, - options.framework.optionalPackages.tailwindcss, + 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( @@ -132,7 +146,8 @@ export function createPackageJSON(options: Options) { packageManager: options.packageManager, projectName: options.projectName, typescript: true, - tailwind: true, + tailwind: options.tailwind, + blank, js: 'ts', jsx: 'tsx', fileRouter: options.mode === 'file-router', diff --git a/packages/create/src/edge-render.ts b/packages/create/src/edge-render.ts index db47fa10..6133bf9f 100644 --- a/packages/create/src/edge-render.ts +++ b/packages/create/src/edge-render.ts @@ -9,6 +9,7 @@ export type TemplateRenderContext = { projectName: string | undefined typescript: boolean | undefined tailwind: boolean | undefined + blank: boolean | undefined js: string | undefined jsx: string | undefined fileRouter: boolean | undefined @@ -92,6 +93,7 @@ function renderWithRenderer( projectName: undefined, typescript: undefined, tailwind: undefined, + blank: undefined, js: undefined, jsx: undefined, fileRouter: undefined, diff --git a/packages/create/src/edge-template-file.ts b/packages/create/src/edge-template-file.ts index bf7d67de..916a2342 100644 --- a/packages/create/src/edge-template-file.ts +++ b/packages/create/src/edge-template-file.ts @@ -128,13 +128,15 @@ export function createTemplateFile(environment: Environment, options: Options) { packageManager: options.packageManager, projectName: options.projectName, typescript: true, - tailwind: true, + tailwind: options.tailwind, + blank: options.projectPreset === 'blank', js: 'ts', jsx: 'tsx', fileRouter: options.mode === 'file-router', codeRouter: options.mode === 'code-router', routerOnly: options.routerOnly === true, - includeExamples: options.includeExamples !== false, + includeExamples: + options.projectPreset !== 'blank' && options.includeExamples !== false, addOnEnabled, addOnOption: options.addOnOptions, addOns: options.chosenAddOns, diff --git a/packages/create/src/frameworks/react/add-ons/posthog/info.json b/packages/create/src/frameworks/react/add-ons/posthog/info.json index fad5e154..de375e95 100644 --- a/packages/create/src/frameworks/react/add-ons/posthog/info.json +++ b/packages/create/src/frameworks/react/add-ons/posthog/info.json @@ -8,7 +8,6 @@ "color": "#1D4AFF", "priority": 20, "link": "https://posthog.com", - "tailwind": true, "routes": [ { "icon": "BarChart", diff --git a/packages/create/src/frameworks/react/add-ons/shadcn/info.json b/packages/create/src/frameworks/react/add-ons/shadcn/info.json index 958b2d2b..923029c9 100644 --- a/packages/create/src/frameworks/react/add-ons/shadcn/info.json +++ b/packages/create/src/frameworks/react/add-ons/shadcn/info.json @@ -6,6 +6,7 @@ "link": "https://ui.shadcn.com/", "type": "add-on", "category": "styling", + "tailwind": true, "color": "#000000", "priority": 30 } diff --git a/packages/create/src/frameworks/react/add-ons/shadcn/package.json b/packages/create/src/frameworks/react/add-ons/shadcn/package.json index ed2a10d9..d913c699 100644 --- a/packages/create/src/frameworks/react/add-ons/shadcn/package.json +++ b/packages/create/src/frameworks/react/add-ons/shadcn/package.json @@ -5,5 +5,8 @@ "lucide-react": "^0.577.0", "tailwind-merge": "^3.0.2", "tw-animate-css": "^1.3.6" + }, + "devDependencies": { + "@tailwindcss/typography": "^0.5.16" } } diff --git a/packages/create/src/frameworks/react/add-ons/shopify/info.json b/packages/create/src/frameworks/react/add-ons/shopify/info.json index 4647ddac..e037764d 100644 --- a/packages/create/src/frameworks/react/add-ons/shopify/info.json +++ b/packages/create/src/frameworks/react/add-ons/shopify/info.json @@ -5,6 +5,7 @@ "modes": ["file-router"], "type": "add-on", "category": "other", + "tailwind": true, "color": "#5A31F4", "priority": 80, "link": "https://shopify.dev/docs/storefronts/headless", diff --git a/packages/create/src/frameworks/react/add-ons/strapi/package.json b/packages/create/src/frameworks/react/add-ons/strapi/package.json index 91573589..a8966a5c 100644 --- a/packages/create/src/frameworks/react/add-ons/strapi/package.json +++ b/packages/create/src/frameworks/react/add-ons/strapi/package.json @@ -1,6 +1,7 @@ { "dependencies": { "@strapi/client": "^1.6.1", + "lucide-react": "^0.577.0", "react-markdown": "^9.0.1", "remark-gfm": "^4.0.0", "use-debounce": "^10.1.0" diff --git a/packages/create/src/frameworks/react/add-ons/tanstack-query/package.json b/packages/create/src/frameworks/react/add-ons/tanstack-query/package.json index 1210c53d..b3f749ad 100644 --- a/packages/create/src/frameworks/react/add-ons/tanstack-query/package.json +++ b/packages/create/src/frameworks/react/add-ons/tanstack-query/package.json @@ -1,6 +1,7 @@ { "dependencies": { "@tanstack/react-query": "latest", - "@tanstack/react-query-devtools": "latest" + "@tanstack/react-query-devtools": "latest", + "@tanstack/react-router-ssr-query": "latest" } } diff --git a/packages/create/src/frameworks/react/examples/events/package.json b/packages/create/src/frameworks/react/examples/events/package.json index 75464d5d..fe624d33 100644 --- a/packages/create/src/frameworks/react/examples/events/package.json +++ b/packages/create/src/frameworks/react/examples/events/package.json @@ -10,6 +10,7 @@ "@tanstack/store": "latest", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", + "lucide-react": "^0.577.0", "marked": "^15.0.8", "streamdown": "^2.3.0", "tailwind-merge": "^3.0.2", diff --git a/packages/create/src/frameworks/react/examples/resume/package.json b/packages/create/src/frameworks/react/examples/resume/package.json index 6e5bd767..4b845ca6 100644 --- a/packages/create/src/frameworks/react/examples/resume/package.json +++ b/packages/create/src/frameworks/react/examples/resume/package.json @@ -14,6 +14,7 @@ "@tanstack/store": "latest", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", + "lucide-react": "^0.577.0", "marked": "^15.0.8", "streamdown": "^2.3.0", "tailwind-merge": "^3.0.2", diff --git a/packages/create/src/frameworks/react/hosts/cloudflare/package.json.ejs b/packages/create/src/frameworks/react/hosts/cloudflare/package.json.ejs index 83540cb5..5a15c454 100644 --- a/packages/create/src/frameworks/react/hosts/cloudflare/package.json.ejs +++ b/packages/create/src/frameworks/react/hosts/cloudflare/package.json.ejs @@ -2,10 +2,8 @@ "scripts": { "deploy": "<%= packageManager %> run build && wrangler deploy" }, - "dependencies": { - "@cloudflare/vite-plugin": "^1.26.0" - }, "devDependencies": { + "@cloudflare/vite-plugin": "^1.26.0", "wrangler": "^4.70.0" } } diff --git a/packages/create/src/frameworks/react/project/base/README.md.ejs b/packages/create/src/frameworks/react/project/base/README.md.ejs index ebdcd6aa..d8c38408 100644 --- a/packages/create/src/frameworks/react/project/base/README.md.ejs +++ b/packages/create/src/frameworks/react/project/base/README.md.ejs @@ -1,28 +1,42 @@ -Welcome to your new TanStack Start app! +<% if (blank) { -%> +# <%= projectName %> -# Getting Started - -To run this application: +A minimal TanStack Start app with one route and plain CSS. ```bash <%= packageManager %> install <%= getPackageManagerRunScript('dev') %> ``` -# Building For Production +Edit `src/routes/index.tsx` to get started. Add route files under +`src/routes`; TanStack Router updates `src/routeTree.gen.ts` for you. -To build this application for production: +Build the production app with: ```bash <%= getPackageManagerRunScript('build') %> ``` +<% for(const addon of addOns.filter(addon => addon.readme)) { %> +<%- addon.readmeIsEjs ? renderTemplate(addon.readme) : addon.readme %> +<% } %> +<% } else { -%> +Welcome to your new TanStack Start app! -## Testing +# Getting Started -This project uses [Vitest](https://vitest.dev/) for testing. You can run the tests with: +To run this application: ```bash -<%= getPackageManagerRunScript('test') %> +<%= packageManager %> install +<%= getPackageManagerRunScript('dev') %> +``` + +# Building For Production + +To build this application for production: + +```bash +<%= getPackageManagerRunScript('build') %> ``` ## Styling @@ -36,7 +50,7 @@ If you prefer not to use Tailwind CSS: 1. Remove the demo pages in `src/routes/demo/` 2. Replace the Tailwind import in `src/styles.css` with your own styles 3. Remove `tailwindcss()` from the plugins array in `vite.config.ts` -4. Uninstall the packages: `<%= getPackageManagerAddScript('@tailwindcss/vite tailwindcss', true) %>` +4. Remove `@tailwindcss/vite` and `tailwindcss` from `package.json` <% if (addOnEnabled.biome || addOnEnabled.eslint) { %> ## Linting & Formatting <% if (addOnEnabled.biome) { %> @@ -197,12 +211,15 @@ function PeopleComponent() { Loaders simplify your data fetching logic dramatically. Check out more information in the [Loader documentation](https://tanstack.com/router/latest/docs/framework/react/guide/data-loading#loader-parameters). +<% if (includeExamples) { %> # Demo files Files prefixed with `demo` can be safely deleted. They are there to provide a starting point for you to play around with the features you've installed. +<% } %> # Learn More You can learn more about all of the offerings from TanStack in the [TanStack documentation](https://tanstack.com). For TanStack Start specific documentation, visit [TanStack Start](https://tanstack.com/start). +<% } -%> diff --git a/packages/create/src/frameworks/react/project/base/package.json b/packages/create/src/frameworks/react/project/base/package.json index 139568dd..0b33ecb1 100644 --- a/packages/create/src/frameworks/react/project/base/package.json +++ b/packages/create/src/frameworks/react/project/base/package.json @@ -9,31 +9,17 @@ "dev": "vite dev --port 3000", "generate-routes": "tsr generate", "build": "vite build", - "preview": "vite preview", - "test": "vitest run" + "preview": "vite preview" }, "dependencies": { - "@tanstack/react-devtools": "latest", "@tanstack/react-router": "latest", - "@tanstack/react-router-devtools": "latest", - "@tanstack/react-router-ssr-query": "latest", "@tanstack/react-start": "latest", - "lucide-react": "^0.577.0", "react": "^19.2.0", "react-dom": "^19.2.0" }, "devDependencies": { - "@tailwindcss/typography": "^0.5.16", - "@tanstack/devtools-vite": "latest", - "@testing-library/dom": "^10.4.1", - "@testing-library/react": "^16.3.0", - "@types/react": "^19.2.0", - "@types/react-dom": "^19.2.0", "@vitejs/plugin-react": "^6.0.1", - "jsdom": "^28.1.0", - "typescript": "^6.0.2", - "vite": "^8.0.0", - "vitest": "^4.1.5" + "vite": "^8.0.0" }, "pnpm": { "onlyBuiltDependencies": [ diff --git a/packages/create/src/frameworks/react/project/base/public/favicon.ico b/packages/create/src/frameworks/react/project/base/public/favicon.ico deleted file mode 100644 index a11777cc471a4344702741ab1c8a588998b1311a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3870 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 baj7?x%VD!5Cxk2YbYLNVeiXvvpMCWYo=by@ diff --git a/packages/create/src/frameworks/react/project/base/public/logo192.png b/packages/create/src/frameworks/react/project/base/public/logo192.png deleted file mode 100644 index fc44b0a3796c0e0a64c3d858ca038bd4570465d9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5347 zcmZWtbyO6NvR-oO24RV%BvuJ&=?+<7=`LvyB&A_#M7mSDYw1v6DJkiYl9XjT!%$dLEBTQ8R9|wd3008in6lFF3GV-6mLi?MoP_y~}QUnaDCHI#t z7w^m$@6DI)|C8_jrT?q=f8D?0AM?L)Z}xAo^e^W>t$*Y0KlT5=@bBjT9kxb%-KNdk zeOS1tKO#ChhG7%{ApNBzE2ZVNcxbrin#E1TiAw#BlUhXllzhN$qWez5l;h+t^q#Eav8PhR2|T}y5kkflaK`ba-eoE+Z2q@o6P$)=&` z+(8}+-McnNO>e#$Rr{32ngsZIAX>GH??tqgwUuUz6kjns|LjsB37zUEWd|(&O!)DY zQLrq%Y>)Y8G`yYbYCx&aVHi@-vZ3|ebG!f$sTQqMgi0hWRJ^Wc+Ibv!udh_r%2|U) zPi|E^PK?UE!>_4`f`1k4hqqj_$+d!EB_#IYt;f9)fBOumGNyglU(ofY`yHq4Y?B%- zp&G!MRY<~ajTgIHErMe(Z8JG*;D-PJhd@RX@QatggM7+G(Lz8eZ;73)72Hfx5KDOE zkT(m}i2;@X2AT5fW?qVp?@WgN$aT+f_6eo?IsLh;jscNRp|8H}Z9p_UBO^SJXpZew zEK8fz|0Th%(Wr|KZBGTM4yxkA5CFdAj8=QSrT$fKW#tweUFqr0TZ9D~a5lF{)%-tTGMK^2tz(y2v$i%V8XAxIywrZCp=)83p(zIk6@S5AWl|Oa2hF`~~^W zI;KeOSkw1O#TiQ8;U7OPXjZM|KrnN}9arP)m0v$c|L)lF`j_rpG(zW1Qjv$=^|p*f z>)Na{D&>n`jOWMwB^TM}slgTEcjxTlUby89j1)|6ydRfWERn3|7Zd2&e7?!K&5G$x z`5U3uFtn4~SZq|LjFVrz$3iln-+ucY4q$BC{CSm7Xe5c1J<=%Oagztj{ifpaZk_bQ z9Sb-LaQMKp-qJA*bP6DzgE3`}*i1o3GKmo2pn@dj0;He}F=BgINo};6gQF8!n0ULZ zL>kC0nPSFzlcB7p41doao2F7%6IUTi_+!L`MM4o*#Y#0v~WiO8uSeAUNp=vA2KaR&=jNR2iVwG>7t%sG2x_~yXzY)7K& zk3p+O0AFZ1eu^T3s};B%6TpJ6h-Y%B^*zT&SN7C=N;g|#dGIVMSOru3iv^SvO>h4M=t-N1GSLLDqVTcgurco6)3&XpU!FP6Hlrmj}f$ zp95;b)>M~`kxuZF3r~a!rMf4|&1=uMG$;h^g=Kl;H&Np-(pFT9FF@++MMEx3RBsK?AU0fPk-#mdR)Wdkj)`>ZMl#^<80kM87VvsI3r_c@_vX=fdQ`_9-d(xiI z4K;1y1TiPj_RPh*SpDI7U~^QQ?%0&!$Sh#?x_@;ag)P}ZkAik{_WPB4rHyW#%>|Gs zdbhyt=qQPA7`?h2_8T;-E6HI#im9K>au*(j4;kzwMSLgo6u*}-K`$_Gzgu&XE)udQ zmQ72^eZd|vzI)~!20JV-v-T|<4@7ruqrj|o4=JJPlybwMg;M$Ud7>h6g()CT@wXm` zbq=A(t;RJ^{Xxi*Ff~!|3!-l_PS{AyNAU~t{h;(N(PXMEf^R(B+ZVX3 z8y0;0A8hJYp@g+c*`>eTA|3Tgv9U8#BDTO9@a@gVMDxr(fVaEqL1tl?md{v^j8aUv zm&%PX4^|rX|?E4^CkplWWNv*OKM>DxPa z!RJ)U^0-WJMi)Ksc!^ixOtw^egoAZZ2Cg;X7(5xZG7yL_;UJ#yp*ZD-;I^Z9qkP`} zwCTs0*%rIVF1sgLervtnUo&brwz?6?PXRuOCS*JI-WL6GKy7-~yi0giTEMmDs_-UX zo=+nFrW_EfTg>oY72_4Z0*uG>MnXP=c0VpT&*|rvv1iStW;*^={rP1y?Hv+6R6bxFMkxpWkJ>m7Ba{>zc_q zEefC3jsXdyS5??Mz7IET$Kft|EMNJIv7Ny8ZOcKnzf`K5Cd)&`-fTY#W&jnV0l2vt z?Gqhic}l}mCv1yUEy$%DP}4AN;36$=7aNI^*AzV(eYGeJ(Px-j<^gSDp5dBAv2#?; zcMXv#aj>%;MiG^q^$0MSg-(uTl!xm49dH!{X0){Ew7ThWV~Gtj7h%ZD zVN-R-^7Cf0VH!8O)uUHPL2mO2tmE*cecwQv_5CzWeh)ykX8r5Hi`ehYo)d{Jnh&3p z9ndXT$OW51#H5cFKa76c<%nNkP~FU93b5h-|Cb}ScHs@4Q#|}byWg;KDMJ#|l zE=MKD*F@HDBcX@~QJH%56eh~jfPO-uKm}~t7VkHxHT;)4sd+?Wc4* z>CyR*{w@4(gnYRdFq=^(#-ytb^5ESD?x<0Skhb%Pt?npNW1m+Nv`tr9+qN<3H1f<% zZvNEqyK5FgPsQ`QIu9P0x_}wJR~^CotL|n zk?dn;tLRw9jJTur4uWoX6iMm914f0AJfB@C74a;_qRrAP4E7l890P&{v<}>_&GLrW z)klculcg`?zJO~4;BBAa=POU%aN|pmZJn2{hA!d!*lwO%YSIzv8bTJ}=nhC^n}g(ld^rn#kq9Z3)z`k9lvV>y#!F4e{5c$tnr9M{V)0m(Z< z#88vX6-AW7T2UUwW`g<;8I$Jb!R%z@rCcGT)-2k7&x9kZZT66}Ztid~6t0jKb&9mm zpa}LCb`bz`{MzpZR#E*QuBiZXI#<`5qxx=&LMr-UUf~@dRk}YI2hbMsAMWOmDzYtm zjof16D=mc`^B$+_bCG$$@R0t;e?~UkF?7<(vkb70*EQB1rfUWXh$j)R2)+dNAH5%R zEBs^?N;UMdy}V};59Gu#0$q53$}|+q7CIGg_w_WlvE}AdqoS<7DY1LWS9?TrfmcvT zaypmplwn=P4;a8-%l^e?f`OpGb}%(_mFsL&GywhyN(-VROj`4~V~9bGv%UhcA|YW% zs{;nh@aDX11y^HOFXB$a7#Sr3cEtNd4eLm@Y#fc&j)TGvbbMwze zXtekX_wJqxe4NhuW$r}cNy|L{V=t#$%SuWEW)YZTH|!iT79k#?632OFse{+BT_gau zJwQcbH{b}dzKO?^dV&3nTILYlGw{27UJ72ZN){BILd_HV_s$WfI2DC<9LIHFmtyw? zQ;?MuK7g%Ym+4e^W#5}WDLpko%jPOC=aN)3!=8)s#Rnercak&b3ESRX3z{xfKBF8L z5%CGkFmGO@x?_mPGlpEej!3!AMddChabyf~nJNZxx!D&{@xEb!TDyvqSj%Y5@A{}9 zRzoBn0?x}=krh{ok3Nn%e)#~uh;6jpezhA)ySb^b#E>73e*frBFu6IZ^D7Ii&rsiU z%jzygxT-n*joJpY4o&8UXr2s%j^Q{?e-voloX`4DQyEK+DmrZh8A$)iWL#NO9+Y@!sO2f@rI!@jN@>HOA< z?q2l{^%mY*PNx2FoX+A7X3N}(RV$B`g&N=e0uvAvEN1W^{*W?zT1i#fxuw10%~))J zjx#gxoVlXREWZf4hRkgdHx5V_S*;p-y%JtGgQ4}lnA~MBz-AFdxUxU1RIT$`sal|X zPB6sEVRjGbXIP0U+?rT|y5+ev&OMX*5C$n2SBPZr`jqzrmpVrNciR0e*Wm?fK6DY& zl(XQZ60yWXV-|Ps!A{EF;=_z(YAF=T(-MkJXUoX zI{UMQDAV2}Ya?EisdEW;@pE6dt;j0fg5oT2dxCi{wqWJ<)|SR6fxX~5CzblPGr8cb zUBVJ2CQd~3L?7yfTpLNbt)He1D>*KXI^GK%<`bq^cUq$Q@uJifG>p3LU(!H=C)aEL zenk7pVg}0{dKU}&l)Y2Y2eFMdS(JS0}oZUuVaf2+K*YFNGHB`^YGcIpnBlMhO7d4@vV zv(@N}(k#REdul8~fP+^F@ky*wt@~&|(&&meNO>rKDEnB{ykAZ}k>e@lad7to>Ao$B zz<1(L=#J*u4_LB=8w+*{KFK^u00NAmeNN7pr+Pf+N*Zl^dO{LM-hMHyP6N!~`24jd zXYP|Ze;dRXKdF2iJG$U{k=S86l@pytLx}$JFFs8e)*Vi?aVBtGJ3JZUj!~c{(rw5>vuRF$`^p!P8w1B=O!skwkO5yd4_XuG^QVF z`-r5K7(IPSiKQ2|U9+`@Js!g6sfJwAHVd|s?|mnC*q zp|B|z)(8+mxXyxQ{8Pg3F4|tdpgZZSoU4P&9I8)nHo1@)9_9u&NcT^FI)6|hsAZFk zZ+arl&@*>RXBf-OZxhZerOr&dN5LW9@gV=oGFbK*J+m#R-|e6(Loz(;g@T^*oO)0R zN`N=X46b{7yk5FZGr#5&n1!-@j@g02g|X>MOpF3#IjZ_4wg{dX+G9eqS+Es9@6nC7 zD9$NuVJI}6ZlwtUm5cCAiYv0(Yi{%eH+}t)!E^>^KxB5^L~a`4%1~5q6h>d;paC9c zTj0wTCKrhWf+F#5>EgX`sl%POl?oyCq0(w0xoL?L%)|Q7d|Hl92rUYAU#lc**I&^6p=4lNQPa0 znQ|A~i0ip@`B=FW-Q;zh?-wF;Wl5!+q3GXDu-x&}$gUO)NoO7^$BeEIrd~1Dh{Tr` z8s<(Bn@gZ(mkIGnmYh_ehXnq78QL$pNDi)|QcT*|GtS%nz1uKE+E{7jdEBp%h0}%r zD2|KmYGiPa4;md-t_m5YDz#c*oV_FqXd85d@eub?9N61QuYcb3CnVWpM(D-^|CmkL z(F}L&N7qhL2PCq)fRh}XO@U`Yn<?TNGR4L(mF7#4u29{i~@k;pLsgl({YW5`Mo+p=zZn3L*4{JU;++dG9 X@eDJUQo;Ye2mwlRs?y0|+_a0zY+Zo%Dkae}+MySoIppb75o?vUW_?)>@g{U2`ERQIXV zeY$JrWnMZ$QC<=ii4X|@0H8`si75jB(ElJb00HAB%>SlLR{!zO|C9P3zxw_U8?1d8uRZ=({Ga4shyN}3 zAK}WA(ds|``G4jA)9}Bt2Hy0+f3rV1E6b|@?hpGA=PI&r8)ah|)I2s(P5Ic*Ndhn^ z*T&j@gbCTv7+8rpYbR^Ty}1AY)YH;p!m948r#%7x^Z@_-w{pDl|1S4`EM3n_PaXvK z1JF)E3qy$qTj5Xs{jU9k=y%SQ0>8E$;x?p9ayU0bZZeo{5Z@&FKX>}s!0+^>C^D#z z>xsCPvxD3Z=dP}TTOSJhNTPyVt14VCQ9MQFN`rn!c&_p?&4<5_PGm4a;WS&1(!qKE z_H$;dDdiPQ!F_gsN`2>`X}$I=B;={R8%L~`>RyKcS$72ai$!2>d(YkciA^J0@X%G4 z4cu!%Ps~2JuJ8ex`&;Fa0NQOq_nDZ&X;^A=oc1&f#3P1(!5il>6?uK4QpEG8z0Rhu zvBJ+A9RV?z%v?!$=(vcH?*;vRs*+PPbOQ3cdPr5=tOcLqmfx@#hOqX0iN)wTTO21jH<>jpmwRIAGw7`a|sl?9y9zRBh>(_%| zF?h|P7}~RKj?HR+q|4U`CjRmV-$mLW>MScKnNXiv{vD3&2@*u)-6P@h0A`eeZ7}71 zK(w%@R<4lLt`O7fs1E)$5iGb~fPfJ?WxhY7c3Q>T-w#wT&zW522pH-B%r5v#5y^CF zcC30Se|`D2mY$hAlIULL%-PNXgbbpRHgn<&X3N9W!@BUk@9g*P5mz-YnZBb*-$zMM z7Qq}ic0mR8n{^L|=+diODdV}Q!gwr?y+2m=3HWwMq4z)DqYVg0J~^}-%7rMR@S1;9 z7GFj6K}i32X;3*$SmzB&HW{PJ55kT+EI#SsZf}bD7nW^Haf}_gXciYKX{QBxIPSx2Ma? zHQqgzZq!_{&zg{yxqv3xq8YV+`S}F6A>Gtl39_m;K4dA{pP$BW0oIXJ>jEQ!2V3A2 zdpoTxG&V=(?^q?ZTj2ZUpDUdMb)T?E$}CI>r@}PFPWD9@*%V6;4Ag>D#h>!s)=$0R zRXvdkZ%|c}ubej`jl?cS$onl9Tw52rBKT)kgyw~Xy%z62Lr%V6Y=f?2)J|bZJ5(Wx zmji`O;_B+*X@qe-#~`HFP<{8$w@z4@&`q^Q-Zk8JG3>WalhnW1cvnoVw>*R@c&|o8 zZ%w!{Z+MHeZ*OE4v*otkZqz11*s!#s^Gq>+o`8Z5 z^i-qzJLJh9!W-;SmFkR8HEZJWiXk$40i6)7 zZpr=k2lp}SasbM*Nbn3j$sn0;rUI;%EDbi7T1ZI4qL6PNNM2Y%6{LMIKW+FY_yF3) zSKQ2QSujzNMSL2r&bYs`|i2Dnn z=>}c0>a}>|uT!IiMOA~pVT~R@bGlm}Edf}Kq0?*Af6#mW9f9!}RjW7om0c9Qlp;yK z)=XQs(|6GCadQbWIhYF=rf{Y)sj%^Id-ARO0=O^Ad;Ph+ z0?$eE1xhH?{T$QI>0JP75`r)U_$#%K1^BQ8z#uciKf(C701&RyLQWBUp*Q7eyn76} z6JHpC9}R$J#(R0cDCkXoFSp;j6{x{b&0yE@P7{;pCEpKjS(+1RQy38`=&Yxo%F=3y zCPeefABp34U-s?WmU#JJw23dcC{sPPFc2#J$ZgEN%zod}J~8dLm*fx9f6SpO zn^Ww3bt9-r0XaT2a@Wpw;C23XM}7_14#%QpubrIw5aZtP+CqIFmsG4`Cm6rfxl9n5 z7=r2C-+lM2AB9X0T_`?EW&Byv&K?HS4QLoylJ|OAF z`8atBNTzJ&AQ!>sOo$?^0xj~D(;kS$`9zbEGd>f6r`NC3X`tX)sWgWUUOQ7w=$TO&*j;=u%25ay-%>3@81tGe^_z*C7pb9y*Ed^H3t$BIKH2o+olp#$q;)_ zfpjCb_^VFg5fU~K)nf*d*r@BCC>UZ!0&b?AGk_jTPXaSnCuW110wjHPPe^9R^;jo3 zwvzTl)C`Zl5}O2}3lec=hZ*$JnkW#7enKKc)(pM${_$9Hc=Sr_A9Biwe*Y=T?~1CK z6eZ9uPICjy-sMGbZl$yQmpB&`ouS8v{58__t0$JP%i3R&%QR3ianbZqDs<2#5FdN@n5bCn^ZtH992~5k(eA|8|@G9u`wdn7bnpg|@{m z^d6Y`*$Zf2Xr&|g%sai#5}Syvv(>Jnx&EM7-|Jr7!M~zdAyjt*xl;OLhvW-a%H1m0 z*x5*nb=R5u><7lyVpNAR?q@1U59 zO+)QWwL8t zyip?u_nI+K$uh{y)~}qj?(w0&=SE^8`_WMM zTybjG=999h38Yes7}-4*LJ7H)UE8{mE(6;8voE+TYY%33A>S6`G_95^5QHNTo_;Ao ztIQIZ_}49%{8|=O;isBZ?=7kfdF8_@azfoTd+hEJKWE!)$)N%HIe2cplaK`ry#=pV z0q{9w-`i0h@!R8K3GC{ivt{70IWG`EP|(1g7i_Q<>aEAT{5(yD z=!O?kq61VegV+st@XCw475j6vS)_z@efuqQgHQR1T4;|-#OLZNQJPV4k$AX1Uk8Lm z{N*b*ia=I+MB}kWpupJ~>!C@xEN#Wa7V+7{m4j8c?)ChV=D?o~sjT?0C_AQ7B-vxqX30s0I_`2$in86#`mAsT-w?j{&AL@B3$;P z31G4(lV|b}uSDCIrjk+M1R!X7s4Aabn<)zpgT}#gE|mIvV38^ODy@<&yflpCwS#fRf9ZX3lPV_?8@C5)A;T zqmouFLFk;qIs4rA=hh=GL~sCFsXHsqO6_y~*AFt939UYVBSx1s(=Kb&5;j7cSowdE;7()CC2|-i9Zz+_BIw8#ll~-tyH?F3{%`QCsYa*b#s*9iCc`1P1oC26?`g<9))EJ3%xz+O!B3 zZ7$j~To)C@PquR>a1+Dh>-a%IvH_Y7^ys|4o?E%3`I&ADXfC8++hAdZfzIT#%C+Jz z1lU~K_vAm0m8Qk}K$F>|>RPK%<1SI0(G+8q~H zAsjezyP+u!Se4q3GW)`h`NPSRlMoBjCzNPesWJwVTY!o@G8=(6I%4XHGaSiS3MEBK zhgGFv6Jc>L$4jVE!I?TQuwvz_%CyO!bLh94nqK11C2W$*aa2ueGopG8DnBICVUORP zgytv#)49fVXDaR$SukloYC3u7#5H)}1K21=?DKj^U)8G;MS)&Op)g^zR2($<>C*zW z;X7`hLxiIO#J`ANdyAOJle4V%ppa*(+0i3w;8i*BA_;u8gOO6)MY`ueq7stBMJTB; z-a0R>hT*}>z|Gg}@^zDL1MrH+2hsR8 zHc}*9IvuQC^Ju)^#Y{fOr(96rQNPNhxc;mH@W*m206>Lo<*SaaH?~8zg&f&%YiOEG zGiz?*CP>Bci}!WiS=zj#K5I}>DtpregpP_tfZtPa(N<%vo^#WCQ5BTv0vr%Z{)0q+ z)RbfHktUm|lg&U3YM%lMUM(fu}i#kjX9h>GYctkx9Mt_8{@s%!K_EI zScgwy6%_fR?CGJQtmgNAj^h9B#zmaMDWgH55pGuY1Gv7D z;8Psm(vEPiwn#MgJYu4Ty9D|h!?Rj0ddE|&L3S{IP%H4^N!m`60ZwZw^;eg4sk6K{ ziA^`Sbl_4~f&Oo%n;8Ye(tiAdlZKI!Z=|j$5hS|D$bDJ}p{gh$KN&JZYLUjv4h{NY zBJ>X9z!xfDGY z+oh_Z&_e#Q(-}>ssZfm=j$D&4W4FNy&-kAO1~#3Im;F)Nwe{(*75(p=P^VI?X0GFakfh+X-px4a%Uw@fSbmp9hM1_~R>?Z8+ ziy|e9>8V*`OP}4x5JjdWp}7eX;lVxp5qS}0YZek;SNmm7tEeSF*-dI)6U-A%m6YvCgM(}_=k#a6o^%-K4{`B1+}O4x zztDT%hVb;v#?j`lTvlFQ3aV#zkX=7;YFLS$uIzb0E3lozs5`Xy zi~vF+%{z9uLjKvKPhP%x5f~7-Gj+%5N`%^=yk*Qn{`> z;xj&ROY6g`iy2a@{O)V(jk&8#hHACVDXey5a+KDod_Z&}kHM}xt7}Md@pil{2x7E~ zL$k^d2@Ec2XskjrN+IILw;#7((abu;OJii&v3?60x>d_Ma(onIPtcVnX@ELF0aL?T zSmWiL3(dOFkt!x=1O!_0n(cAzZW+3nHJ{2S>tgSK?~cFha^y(l@-Mr2W$%MN{#af8J;V*>hdq!gx=d0h$T7l}>91Wh07)9CTX zh2_ZdQCyFOQ)l(}gft0UZG`Sh2`x-w`5vC2UD}lZs*5 zG76$akzn}Xi))L3oGJ75#pcN=cX3!=57$Ha=hQ2^lwdyU#a}4JJOz6ddR%zae%#4& za)bFj)z=YQela(F#Y|Q#dp}PJghITwXouVaMq$BM?K%cXn9^Y@g43$=O)F&ZlOUom zJiad#dea;-eywBA@e&D6Pdso1?2^(pXiN91?jvcaUyYoKUmvl5G9e$W!okWe*@a<^ z8cQQ6cNSf+UPDx%?_G4aIiybZHHagF{;IcD(dPO!#=u zWfqLcPc^+7Uu#l(Bpxft{*4lv#*u7X9AOzDO z1D9?^jIo}?%iz(_dwLa{ex#T}76ZfN_Z-hwpus9y+4xaUu9cX}&P{XrZVWE{1^0yw zO;YhLEW!pJcbCt3L8~a7>jsaN{V3>tz6_7`&pi%GxZ=V3?3K^U+*ryLSb)8^IblJ0 zSRLNDvIxt)S}g30?s_3NX>F?NKIGrG_zB9@Z>uSW3k2es_H2kU;Rnn%j5qP)!XHKE zPB2mHP~tLCg4K_vH$xv`HbRsJwbZMUV(t=ez;Ec(vyHH)FbfLg`c61I$W_uBB>i^r z&{_P;369-&>23R%qNIULe=1~T$(DA`ev*EWZ6j(B$(te}x1WvmIll21zvygkS%vwG zzkR6Z#RKA2!z!C%M!O>!=Gr0(J0FP=-MN=5t-Ir)of50y10W}j`GtRCsXBakrKtG& zazmITDJMA0C51&BnLY)SY9r)NVTMs);1<=oosS9g31l{4ztjD3#+2H7u_|66b|_*O z;Qk6nalpqdHOjx|K&vUS_6ITgGll;TdaN*ta=M_YtyC)I9Tmr~VaPrH2qb6sd~=AcIxV+%z{E&0@y=DPArw zdV7z(G1hBx7hd{>(cr43^WF%4Y@PXZ?wPpj{OQ#tvc$pABJbvPGvdR`cAtHn)cSEV zrpu}1tJwQ3y!mSmH*uz*x0o|CS<^w%&KJzsj~DU0cLQUxk5B!hWE>aBkjJle8z~;s z-!A=($+}Jq_BTK5^B!`R>!MulZN)F=iXXeUd0w5lUsE5VP*H*oCy(;?S$p*TVvTxwAeWFB$jHyb0593)$zqalVlDX=GcCN1gU0 zlgU)I$LcXZ8Oyc2TZYTPu@-;7<4YYB-``Qa;IDcvydIA$%kHhJKV^m*-zxcvU4viy&Kr5GVM{IT>WRywKQ9;>SEiQD*NqplK-KK4YR`p0@JW)n_{TU3bt0 zim%;(m1=#v2}zTps=?fU5w^(*y)xT%1vtQH&}50ZF!9YxW=&7*W($2kgKyz1mUgfs zfV<*XVVIFnohW=|j+@Kfo!#liQR^x>2yQdrG;2o8WZR+XzU_nG=Ed2rK?ntA;K5B{ z>M8+*A4!Jm^Bg}aW?R?6;@QG@uQ8&oJ{hFixcfEnJ4QH?A4>P=q29oDGW;L;= z9-a0;g%c`C+Ai!UmK$NC*4#;Jp<1=TioL=t^YM)<<%u#hnnfSS`nq63QKGO1L8RzX z@MFDqs1z ztYmxDl@LU)5acvHk)~Z`RW7=aJ_nGD!mOSYD>5Odjn@TK#LY{jf?+piB5AM-CAoT_ z?S-*q7}wyLJzK>N%eMPuFgN)Q_otKP;aqy=D5f!7<=n(lNkYRXVpkB{TAYLYg{|(jtRqYmg$xH zjmq?B(RE4 zQx^~Pt}gxC2~l=K$$-sYy_r$CO(d=+b3H1MB*y_5g6WLaWTXn+TKQ|hNY^>Mp6k*$ zwkovomhu776vQATqT4blf~g;TY(MWCrf^^yfWJvSAB$p5l;jm@o#=!lqw+Lqfq>X= z$6~kxfm7`3q4zUEB;u4qa#BdJxO!;xGm)wwuisj{0y2x{R(IGMrsIzDY9LW>m!Y`= z04sx3IjnYvL<4JqxQ8f7qYd0s2Ig%`ytYPEMKI)s(LD}D@EY>x`VFtqvnADNBdeao zC96X+MxnwKmjpg{U&gP3HE}1=s!lv&D{6(g_lzyF3A`7Jn*&d_kL<;dAFx!UZ>hB8 z5A*%LsAn;VLp>3${0>M?PSQ)9s3}|h2e?TG4_F{}{Cs>#3Q*t$(CUc}M)I}8cPF6% z=+h(Kh^8)}gj(0}#e7O^FQ6`~fd1#8#!}LMuo3A0bN`o}PYsm!Y}sdOz$+Tegc=qT z8x`PH$7lvnhJp{kHWb22l;@7B7|4yL4UOOVM0MP_>P%S1Lnid)+k9{+3D+JFa#Pyf zhVc#&df87APl4W9X)F3pGS>@etfl=_E5tBcVoOfrD4hmVeTY-cj((pkn%n@EgN{0f zwb_^Rk0I#iZuHK!l*lN`ceJn(sI{$Fq6nN& zE<-=0_2WN}m+*ivmIOxB@#~Q-cZ>l136w{#TIJe478`KE7@=a{>SzPHsKLzYAyBQO zAtuuF$-JSDy_S@6GW0MOE~R)b;+0f%_NMrW(+V#c_d&U8Z9+ec4=HmOHw?gdjF(Lu zzra83M_BoO-1b3;9`%&DHfuUY)6YDV21P$C!Rc?mv&{lx#f8oc6?0?x zK08{WP65?#>(vPfA-c=MCY|%*1_<3D4NX zeVTi-JGl2uP_2@0F{G({pxQOXt_d{g_CV6b?jNpfUG9;8yle-^4KHRvZs-_2siata zt+d_T@U$&t*xaD22(fH(W1r$Mo?3dc%Tncm=C6{V9y{v&VT#^1L04vDrLM9qBoZ4@ z6DBN#m57hX7$C(=#$Y5$bJmwA$T8jKD8+6A!-IJwA{WOfs%s}yxUw^?MRZjF$n_KN z6`_bGXcmE#5e4Ym)aQJ)xg3Pg0@k`iGuHe?f(5LtuzSq=nS^5z>vqU0EuZ&75V%Z{ zYyhRLN^)$c6Ds{f7*FBpE;n5iglx5PkHfWrj3`x^j^t z7ntuV`g!9Xg#^3!x)l*}IW=(Tz3>Y5l4uGaB&lz{GDjm2D5S$CExLT`I1#n^lBH7Y zDgpMag@`iETKAI=p<5E#LTkwzVR@=yY|uBVI1HG|8h+d;G-qfuj}-ZR6fN>EfCCW z9~wRQoAPEa#aO?3h?x{YvV*d+NtPkf&4V0k4|L=uj!U{L+oLa(z#&iuhJr3-PjO3R z5s?=nn_5^*^Rawr>>Nr@K(jwkB#JK-=+HqwfdO<+P5byeim)wvqGlP-P|~Nse8=XF zz`?RYB|D6SwS}C+YQv+;}k6$-%D(@+t14BL@vM z2q%q?f6D-A5s$_WY3{^G0F131bbh|g!}#BKw=HQ7mx;Dzg4Z*bTLQSfo{ed{4}NZW zfrRm^Ca$rlE{Ue~uYv>R9{3smwATcdM_6+yWIO z*ZRH~uXE@#p$XTbCt5j7j2=86e{9>HIB6xDzV+vAo&B?KUiMP|ttOElepnl%|DPqL b{|{}U^kRn2wo}j7|0ATu<;8xA7zX}7|B6mN diff --git a/packages/create/src/frameworks/react/project/base/public/manifest.json b/packages/create/src/frameworks/react/project/base/public/manifest.json deleted file mode 100644 index 078ef501..00000000 --- a/packages/create/src/frameworks/react/project/base/public/manifest.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "short_name": "TanStack App", - "name": "Create TanStack App Sample", - "icons": [ - { - "src": "favicon.ico", - "sizes": "64x64 32x32 24x24 16x16", - "type": "image/x-icon" - }, - { - "src": "logo192.png", - "type": "image/png", - "sizes": "192x192" - }, - { - "src": "logo512.png", - "type": "image/png", - "sizes": "512x512" - } - ], - "start_url": ".", - "display": "standalone", - "theme_color": "#000000", - "background_color": "#ffffff" -} diff --git a/packages/create/src/frameworks/react/project/base/public/robots.txt b/packages/create/src/frameworks/react/project/base/public/robots.txt deleted file mode 100644 index e9e57dc4..00000000 --- a/packages/create/src/frameworks/react/project/base/public/robots.txt +++ /dev/null @@ -1,3 +0,0 @@ -# https://www.robotstxt.org/robotstxt.html -User-agent: * -Disallow: diff --git a/packages/create/src/frameworks/react/project/base/src/App.test.tsx.ejs b/packages/create/src/frameworks/react/project/base/src/App.test.tsx.ejs deleted file mode 100644 index e210abeb..00000000 --- a/packages/create/src/frameworks/react/project/base/src/App.test.tsx.ejs +++ /dev/null @@ -1,10 +0,0 @@ -<% if (fileRouter) { ignoreFile() } %>import { describe, expect, test } from "vitest"; -import { render, screen } from "@testing-library/react"; -import App from "./App.<%= jsx %>"; - -describe("App", () => { - test("renders", () => { - render(); - expect(screen.getByText("Learn React")).toBeDefined(); - }); -}); diff --git a/packages/create/src/frameworks/react/project/base/src/routes/__root.tsx.ejs b/packages/create/src/frameworks/react/project/base/src/routes/__root.tsx.ejs index 2b3098ad..aa7f0e11 100644 --- a/packages/create/src/frameworks/react/project/base/src/routes/__root.tsx.ejs +++ b/packages/create/src/frameworks/react/project/base/src/routes/__root.tsx.ejs @@ -1,7 +1,10 @@ +<% const showDevtools = !blank || integrations.some((integration) => integration.type === 'devtools') -%> <% if (routerOnly) { %> import { Outlet, createRootRoute } from '@tanstack/react-router' +<% if (showDevtools) { %> import { TanStackRouterDevtoolsPanel } from '@tanstack/react-router-devtools' import { TanStackDevtools } from '@tanstack/react-devtools' +<% } %> import '../styles.css' @@ -13,7 +16,7 @@ function RootComponent() { return ( <> - , }, ]} - /> + /><% } %> ) } -<% } else if (!includeExamples) { %> +<% } else if (!includeExamples || blank) { %> <% let hasContext = addOnEnabled["apollo-client"] || addOnEnabled["tanstack-query"]; %> import { HeadContent, Scripts, <% if (hasContext) { %>createRootRouteWithContext<% } else { %>createRootRoute<% } %> } from '@tanstack/react-router' -import { TanStackRouterDevtoolsPanel } from '@tanstack/react-router-devtools'; +<% if (showDevtools) { %>import { TanStackRouterDevtoolsPanel } from '@tanstack/react-router-devtools'; import { TanStackDevtools } from '@tanstack/react-devtools' +<% } %> <% for(const integration of integrations.filter(i => i.type === 'layout' || i.type === 'provider' || i.type === 'devtools')) { %> import <%= integration.jsName %> from '<%= relativePath(integration.path, true) %>' <% } %><% if (addOnEnabled.paraglide) { %> @@ -101,7 +105,7 @@ function RootDocument({ children }: { children: React.ReactNode }) { <% for(const integration of integrations.filter(i => i.type === 'provider')) { %><<%= integration.jsName %>> <% } %>{children} - i.type === 'devtools')) { %><%= integration.jsName %>,<% } %> ]} - /> + /><% } %> <% for(const integration of integrations.filter(i => i.type === 'layout')) { %><<%= integration.jsName %> /> <% } %><% for(const integration of integrations.filter(i => i.type === 'provider').reverse()) { %>> <% } %> diff --git a/packages/create/src/frameworks/react/project/base/src/routes/index.tsx.ejs b/packages/create/src/frameworks/react/project/base/src/routes/index.tsx.ejs index 7dd740be..10ad1b88 100644 --- a/packages/create/src/frameworks/react/project/base/src/routes/index.tsx.ejs +++ b/packages/create/src/frameworks/react/project/base/src/routes/index.tsx.ejs @@ -1,4 +1,19 @@ -<% if (!includeExamples) { %> +<% if (blank) { %> +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/')({ component: Home }) + +function Home() { + return ( +
+

Welcome to TanStack Start

+

+ Edit src/routes/index.tsx to get started. +

+
+ ) +} +<% } else if (!includeExamples) { %> import { createFileRoute } from "@tanstack/react-router"; export const Route = createFileRoute("/")({ component: Home }); diff --git a/packages/create/src/frameworks/react/project/base/src/styles.css.ejs b/packages/create/src/frameworks/react/project/base/src/styles.css.ejs index ceb9069c..54a185ff 100644 --- a/packages/create/src/frameworks/react/project/base/src/styles.css.ejs +++ b/packages/create/src/frameworks/react/project/base/src/styles.css.ejs @@ -1,4 +1,21 @@ -<% if (!includeExamples) { %> +<% if (blank) { %> +<% if (tailwind) { %>@import "tailwindcss"; + +<% } %> +* { + box-sizing: border-box; +} + +body { + margin: 0; + font-family: system-ui, sans-serif; +} + +main { + width: min(42rem, calc(100% - 2rem)); + margin: 4rem auto; +} +<% } else if (!includeExamples) { %> @import "tailwindcss"; * { diff --git a/packages/create/src/frameworks/react/project/base/vite.config.ts.ejs b/packages/create/src/frameworks/react/project/base/vite.config.ts.ejs index 3aedff14..0a60f457 100644 --- a/packages/create/src/frameworks/react/project/base/vite.config.ts.ejs +++ b/packages/create/src/frameworks/react/project/base/vite.config.ts.ejs @@ -1,5 +1,8 @@ import { defineConfig } from 'vite' +<% const showDevtools = !blank || integrations.some((integration) => integration.type === 'devtools') -%> +<% if (showDevtools) { -%> import { devtools } from '@tanstack/devtools-vite' +<% } -%> <% if (addOnEnabled.paraglide) { -%> import { paraglideVitePlugin } from "@inlang/paraglide-js" <% } -%> @@ -14,18 +17,20 @@ import babel from '@rolldown/plugin-babel' <% } else { -%> import viteReact from '@vitejs/plugin-react' <% } -%> +<% if (tailwind) { -%> import tailwindcss from "@tailwindcss/vite" +<% } -%> <% for(const integration of integrations.filter(i => i.type === 'vite-plugin')) { %><%- integrationImportContent(integration) %> <% } %> const config = defineConfig({ resolve: { tsconfigPaths: true }, - plugins: [devtools(), <% if (addOnEnabled.paraglide) { %>paraglideVitePlugin({ + plugins: [<% if (showDevtools) { %>devtools(), <% } %><% if (addOnEnabled.paraglide) { %>paraglideVitePlugin({ project: './project.inlang', outdir: './src/paraglide', strategy: ['url', "baseLocale"], }), <% } %><% for(const integration of integrations.filter(i => i.type === 'vite-plugin')) { %><%- integrationImportCode(integration) %>,<% } %> - tailwindcss(), + <% if (tailwind) { %>tailwindcss(),<% } %> <% if (routerOnly) { %>tanstackRouter({ target: 'react', autoCodeSplitting: true }),<% } else { %>tanstackStart(),<% } %> viteReact(),<% if (addOnEnabled.compiler) { %> babel({ presets: [reactCompilerPreset()] }),<% } %> diff --git a/packages/create/src/frameworks/react/project/packages.json b/packages/create/src/frameworks/react/project/packages.json index 2bc6816a..5103bfc7 100644 --- a/packages/create/src/frameworks/react/project/packages.json +++ b/packages/create/src/frameworks/react/project/packages.json @@ -10,16 +10,26 @@ "tailwindcss": { "dependencies": { "@tailwindcss/vite": "^4.1.18", - "tailwindcss": "^4.1.18", - "lucide-react": "^0.545.0" + "tailwindcss": "^4.1.18" + } + }, + "devtools": { + "dependencies": { + "@tanstack/react-devtools": "latest", + "@tanstack/react-router-devtools": "latest" + }, + "devDependencies": { + "@tanstack/devtools-vite": "latest" + } + }, + "examples": { + "devDependencies": { + "@tailwindcss/typography": "^0.5.16" } }, "file-router": { "devDependencies": { "@tanstack/router-cli": "^1.132.0" - }, - "dependencies": { - "@tanstack/router-plugin": "^1.132.0" } } } diff --git a/packages/create/src/frameworks/solid/add-ons/solid-ui/info.json b/packages/create/src/frameworks/solid/add-ons/solid-ui/info.json index 242b5b58..3b5f282f 100644 --- a/packages/create/src/frameworks/solid/add-ons/solid-ui/info.json +++ b/packages/create/src/frameworks/solid/add-ons/solid-ui/info.json @@ -6,5 +6,6 @@ "modes": ["file-router", "code-router"], "type": "add-on", "category": "styling", + "tailwind": true, "color": "#000000" } diff --git a/packages/create/src/frameworks/solid/project/base/README.md.ejs b/packages/create/src/frameworks/solid/project/base/README.md.ejs index c4aabe61..db1dc56f 100644 --- a/packages/create/src/frameworks/solid/project/base/README.md.ejs +++ b/packages/create/src/frameworks/solid/project/base/README.md.ejs @@ -1,4 +1,26 @@ -Welcome to your new TanStack Start app! +<% if (blank) { -%> +# <%= projectName %> + +A minimal TanStack Start app with one route and plain CSS. + +```bash +<%= packageManager %> install +<%= getPackageManagerRunScript('dev') %> +``` + +Edit `src/routes/index.tsx` to get started. Add route files under +`src/routes`; TanStack Router updates `src/routeTree.gen.ts` for you. + +Build the production app with: + +```bash +<%= getPackageManagerRunScript('build') %> +``` +<% for(const addon of addOns.filter(addon => addon.readme)) { %> +<%- addon.readmeIsEjs ? renderTemplate(addon.readme) : addon.readme %> +<% } %> +<% } else { -%> +Welcome to your new TanStack Start app! # Getting Started @@ -28,7 +50,7 @@ If you prefer not to use Tailwind CSS: 1. Remove the demo pages in `src/routes/demo/` 2. Replace the Tailwind import in `src/styles.css` with your own styles 3. Remove `tailwindcss()` from the plugins array in `vite.config.ts` -4. Uninstall the packages: `<%= getPackageManagerAddScript('@tailwindcss/vite tailwindcss', true) %>` +4. Remove `@tailwindcss/vite` and `tailwindcss` from `package.json` <% for(const addon of addOns.filter(addon => addon.readme)) { %> <%- addon.readmeIsEjs ? renderTemplate(addon.readme) : addon.readme %> @@ -115,9 +137,11 @@ function PeopleComponent() { Loaders simplify your data fetching logic dramatically. Check out more information in the [Loader documentation](https://tanstack.com/router/latest/docs/framework/solid/guide/data-loading#loader-parameters). +<% if (includeExamples) { %> # Demo files Files prefixed with `demo` can be safely deleted. They are there to provide a starting point for you to play around with the features you've installed. +<% } %> <% if (addOnEnabled.biome || addOnEnabled.eslint) { %> ## Linting & Formatting @@ -139,3 +163,4 @@ This project uses [eslint](https://eslint.org/) and [prettier](https://prettier. You can learn more about all of the offerings from TanStack in the [TanStack documentation](https://tanstack.com). For TanStack Start specific documentation, visit [TanStack Start](https://tanstack.com/start). +<% } -%> diff --git a/packages/create/src/frameworks/solid/project/base/_dot_cursorrules.append b/packages/create/src/frameworks/solid/project/base/_dot_cursorrules.append deleted file mode 100644 index e859e810..00000000 --- a/packages/create/src/frameworks/solid/project/base/_dot_cursorrules.append +++ /dev/null @@ -1,35 +0,0 @@ -// Solid.js with Tailwind CSS .cursorrules - -// Prefer functional components - -const preferFunctionalComponents = true; - -// Solid.js and Tailwind CSS best practices - -const solidjsTailwindBestPractices = [ -"Use createSignal() for reactive state", -"Implement Tailwind CSS classes for styling", -"Utilize TypeScript's strict mode", -"Utilize @apply directive in CSS files for reusable styles", -"Implement responsive design using Tailwind's responsive classes", -"Use Tailwind's CSS in /src/styles.css for global styles", -"Implement dark mode using Tailwind's dark variant", -]; - -// Additional instructions - -const additionalInstructions = ` - -1. Use .tsx extension for files with JSX -2. Implement strict TypeScript checks -3. Implement proper Tailwind CSS purging for production builds -4. Utilize TanStack Router for routing when applicable -5. Use type-safe context with createContext -6. Implement proper typing for event handlers -7. Follow TypeScript best practices and naming conventions -8. Use type assertions sparingly and only when necessary -9. Use Tailwind's @layer directive for custom styles -10. Implement utility-first CSS approach -11. Follow both Solid.js and Tailwind naming conventions -12. Use JIT (Just-In-Time) mode for faster development - `; diff --git a/packages/create/src/frameworks/solid/project/base/package.json b/packages/create/src/frameworks/solid/project/base/package.json index 5e653255..0c5afcac 100644 --- a/packages/create/src/frameworks/solid/project/base/package.json +++ b/packages/create/src/frameworks/solid/project/base/package.json @@ -7,26 +7,21 @@ "generate-routes": "tsr generate", "build": "vite build", "start": "node .output/server/index.mjs", - "preview": "vite preview", - "test": "vitest run" + "preview": "vite preview" }, "dependencies": { "@tanstack/solid-router": "latest", - "@tanstack/solid-router-devtools": "latest", - "@tanstack/solid-router-ssr-query": "latest", "@tanstack/solid-start": "latest", "solid-js": "^1.9.12" }, "devDependencies": { - "@solidjs/testing-library": "^0.8.10", - "@tanstack/devtools-vite": "latest", - "jsdom": "^28.1.0", "vite-plugin-solid": "^2.11.12", - "typescript": "^6.0.2", - "vite": "^8.0.0", - "vitest": "^4.1.5" + "vite": "^8.0.0" }, "pnpm": { - "onlyBuiltDependencies": ["esbuild", "lightningcss"] + "onlyBuiltDependencies": [ + "esbuild", + "lightningcss" + ] } } diff --git a/packages/create/src/frameworks/solid/project/base/public/favicon.ico b/packages/create/src/frameworks/solid/project/base/public/favicon.ico deleted file mode 100644 index a11777cc471a4344702741ab1c8a588998b1311a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3870 zcma);c{J4h9>;%nil|2-o+rCuEF-(I%-F}ijC~o(k~HKAkr0)!FCj~d>`RtpD?8b; zXOC1OD!V*IsqUwzbMF1)-gEDD=A573Z-&G7^LoAC9|WO7Xc0Cx1g^Zu0u_SjAPB
3vGa^W|sj)80f#V0@M_CAZTIO(t--xg= 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 baj7?x%VD!5Cxk2YbYLNVeiXvvpMCWYo=by@ diff --git a/packages/create/src/frameworks/solid/project/base/public/logo192.png b/packages/create/src/frameworks/solid/project/base/public/logo192.png deleted file mode 100644 index fc44b0a3796c0e0a64c3d858ca038bd4570465d9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5347 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 b{|{}U^kRn2wo}j7|0ATu<;8xA7zX}7|B6mN diff --git a/packages/create/src/frameworks/solid/project/base/public/manifest.json b/packages/create/src/frameworks/solid/project/base/public/manifest.json deleted file mode 100644 index 078ef501..00000000 --- a/packages/create/src/frameworks/solid/project/base/public/manifest.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "short_name": "TanStack App", - "name": "Create TanStack App Sample", - "icons": [ - { - "src": "favicon.ico", - "sizes": "64x64 32x32 24x24 16x16", - "type": "image/x-icon" - }, - { - "src": "logo192.png", - "type": "image/png", - "sizes": "192x192" - }, - { - "src": "logo512.png", - "type": "image/png", - "sizes": "512x512" - } - ], - "start_url": ".", - "display": "standalone", - "theme_color": "#000000", - "background_color": "#ffffff" -} diff --git a/packages/create/src/frameworks/solid/project/base/public/robots.txt b/packages/create/src/frameworks/solid/project/base/public/robots.txt deleted file mode 100644 index e9e57dc4..00000000 --- a/packages/create/src/frameworks/solid/project/base/public/robots.txt +++ /dev/null @@ -1,3 +0,0 @@ -# https://www.robotstxt.org/robotstxt.html -User-agent: * -Disallow: diff --git a/packages/create/src/frameworks/solid/project/base/src/App.test.tsx.ejs b/packages/create/src/frameworks/solid/project/base/src/App.test.tsx.ejs deleted file mode 100644 index 7a28f0aa..00000000 --- a/packages/create/src/frameworks/solid/project/base/src/App.test.tsx.ejs +++ /dev/null @@ -1,10 +0,0 @@ -<% if (fileRouter) { ignoreFile() } %>import { describe, expect, test } from "vitest"; -import { render, screen } from "@solidjs/testing-library"; -import App from "./App.<%= jsx %>"; - -describe("App", () => { - test("renders", () => { - render(() => ); - expect(screen.getByText("Learn Solid")).toBeDefined(); - }); -}); diff --git a/packages/create/src/frameworks/solid/project/base/src/routes/__root.tsx.ejs b/packages/create/src/frameworks/solid/project/base/src/routes/__root.tsx.ejs index 949f34df..55513063 100644 --- a/packages/create/src/frameworks/solid/project/base/src/routes/__root.tsx.ejs +++ b/packages/create/src/frameworks/solid/project/base/src/routes/__root.tsx.ejs @@ -1,6 +1,9 @@ +<% const showDevtools = !blank || integrations.some((integration) => integration.type === 'devtools') -%> <% if (routerOnly) { %> import { Outlet, createRootRoute } from '@tanstack/solid-router' +<% if (showDevtools) { %> import { TanStackRouterDevtools } from '@tanstack/solid-router-devtools' +<% } %> import '../styles.css' @@ -12,13 +15,15 @@ function RootComponent() { return ( <> - + <% if (showDevtools) { %><% } %> ) } -<% } else if (!includeExamples) { %> +<% } else if (!includeExamples || blank) { %> import { HeadContent, Outlet, Scripts, createRootRouteWithContext } from '@tanstack/solid-router' +<% if (showDevtools) { %> import { TanStackRouterDevtools } from '@tanstack/solid-router-devtools' +<% } %> <% if (addOnEnabled['solid-ui']) { %> import "@fontsource/inter/400.css" @@ -51,7 +56,7 @@ function RootComponent() { - + <% if (showDevtools) { %><% } %> <% for(const integration of integrations.filter(i => i.type === 'layout')) { %> <<%= integration.jsName %> /> <% } %> diff --git a/packages/create/src/frameworks/solid/project/base/src/routes/index.tsx.ejs b/packages/create/src/frameworks/solid/project/base/src/routes/index.tsx.ejs index dc0e5446..098c692f 100644 --- a/packages/create/src/frameworks/solid/project/base/src/routes/index.tsx.ejs +++ b/packages/create/src/frameworks/solid/project/base/src/routes/index.tsx.ejs @@ -1,4 +1,19 @@ -<% if (!includeExamples) { %> +<% if (blank) { %> +import { createFileRoute } from '@tanstack/solid-router' + +export const Route = createFileRoute('/')({ component: Home }) + +function Home() { + return ( +
+

Welcome to TanStack Start

+

+ Edit src/routes/index.tsx to get started. +

+
+ ) +} +<% } else if (!includeExamples) { %> import { createFileRoute } from '@tanstack/solid-router' export const Route = createFileRoute('/')({ component: Home }) diff --git a/packages/create/src/frameworks/solid/project/base/src/styles.css.ejs b/packages/create/src/frameworks/solid/project/base/src/styles.css.ejs index 9bbdd0f4..c26f38be 100644 --- a/packages/create/src/frameworks/solid/project/base/src/styles.css.ejs +++ b/packages/create/src/frameworks/solid/project/base/src/styles.css.ejs @@ -1,4 +1,21 @@ -<% if (!includeExamples) { %> +<% if (blank) { %> +<% if (tailwind) { %>@import 'tailwindcss'; + +<% } %> +* { + box-sizing: border-box; +} + +body { + margin: 0; + font-family: system-ui, sans-serif; +} + +main { + width: min(42rem, calc(100% - 2rem)); + margin: 4rem auto; +} +<% } else if (!includeExamples) { %> @import 'tailwindcss'; * { diff --git a/packages/create/src/frameworks/solid/project/base/vite.config.ts.ejs b/packages/create/src/frameworks/solid/project/base/vite.config.ts.ejs index 5fca2364..875ffc02 100644 --- a/packages/create/src/frameworks/solid/project/base/vite.config.ts.ejs +++ b/packages/create/src/frameworks/solid/project/base/vite.config.ts.ejs @@ -1,6 +1,11 @@ import { defineConfig } from "vite"; +<% const showDevtools = !blank || integrations.some((integration) => integration.type === 'devtools') -%> +<% if (showDevtools) { -%> import { devtools } from '@tanstack/devtools-vite' +<% } -%> +<% if (tailwind) { -%> import tailwindcss from "@tailwindcss/vite" +<% } -%> <% if (routerOnly) { %> import { tanstackRouter } from '@tanstack/router-plugin/vite' <% } else { %> @@ -12,8 +17,8 @@ import solidPlugin from 'vite-plugin-solid'; export default defineConfig({ resolve: { tsconfigPaths: true }, - plugins: [devtools(), <% for(const integration of integrations.filter(i => i.type === 'vite-plugin')) { %><%- integrationImportCode(integration) %>,<% } %> - tailwindcss(), + plugins: [<% if (showDevtools) { %>devtools(), <% } %><% for(const integration of integrations.filter(i => i.type === 'vite-plugin')) { %><%- integrationImportCode(integration) %>,<% } %> + <% if (tailwind) { %>tailwindcss(),<% } %> <% if (routerOnly) { %>tanstackRouter({ target: 'solid', autoCodeSplitting: true }),<% } else { %>tanstackStart(),<% } %> solidPlugin(<% if (!routerOnly) { %>{ ssr: true }<% } %>), ], diff --git a/packages/create/src/frameworks/solid/project/packages.json b/packages/create/src/frameworks/solid/project/packages.json index 603bd779..a8f76660 100644 --- a/packages/create/src/frameworks/solid/project/packages.json +++ b/packages/create/src/frameworks/solid/project/packages.json @@ -10,12 +10,17 @@ "tailwindcss": "^4.1.18" } }, + "devtools": { + "dependencies": { + "@tanstack/solid-router-devtools": "latest" + }, + "devDependencies": { + "@tanstack/devtools-vite": "latest" + } + }, "file-router": { "devDependencies": { "@tanstack/router-cli": "^1.133.21" - }, - "dependencies": { - "@tanstack/router-plugin": "^1.133.21" } } } diff --git a/packages/create/src/package-json.ts b/packages/create/src/package-json.ts index a929a487..892d5094 100644 --- a/packages/create/src/package-json.ts +++ b/packages/create/src/package-json.ts @@ -58,6 +58,14 @@ export function mergePackageJSON( 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, @@ -73,7 +81,13 @@ export function createPackageJSON(options: Options) { const additions: Array | undefined> = [ options.framework.optionalPackages.typescript, - options.framework.optionalPackages.tailwindcss, + 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(Boolean)) { @@ -89,7 +103,8 @@ export function createPackageJSON(options: Options) { packageManager: options.packageManager, projectName: options.projectName, typescript: true, - tailwind: true, + tailwind: options.tailwind, + blank, js: 'ts', jsx: 'tsx', fileRouter: options.mode === 'file-router', diff --git a/packages/create/src/template-file.ts b/packages/create/src/template-file.ts index 520a4d23..8713c127 100644 --- a/packages/create/src/template-file.ts +++ b/packages/create/src/template-file.ts @@ -131,13 +131,15 @@ export function createTemplateFile(environment: Environment, options: Options) { packageManager: options.packageManager, projectName: options.projectName, typescript: true, - tailwind: true, + tailwind: options.tailwind, + blank: options.projectPreset === 'blank', js: 'ts', jsx: 'tsx', fileRouter: options.mode === 'file-router', codeRouter: options.mode === 'code-router', routerOnly: options.routerOnly === true, - includeExamples: options.includeExamples !== false, + includeExamples: + options.projectPreset !== 'blank' && options.includeExamples !== false, addOnEnabled, addOnOption: options.addOnOptions, addOns: options.chosenAddOns, diff --git a/packages/create/src/types.ts b/packages/create/src/types.ts index 23721d57..20dcc2c1 100644 --- a/packages/create/src/types.ts +++ b/packages/create/src/types.ts @@ -228,6 +228,7 @@ export interface Options { chosenAddOns: Array addOnOptions: Record> starter?: Starter | undefined + projectPreset?: 'default' | 'blank' routerOnly?: boolean includeExamples?: boolean envVarValues?: Record diff --git a/packages/create/tests/add-to-app.test.ts b/packages/create/tests/add-to-app.test.ts index 07252cb9..e3d9e423 100644 --- a/packages/create/tests/add-to-app.test.ts +++ b/packages/create/tests/add-to-app.test.ts @@ -61,6 +61,7 @@ beforeEach(() => { type: 'add-on', phase: 'add-on', modes: ['code-router', 'file-router'], + tailwind: true, command: { command: 'echo', args: ['baz'], @@ -391,4 +392,34 @@ describe('addToApp', () => { ), }) }) + + it('enables Tailwind when an add-on requires it', async () => { + const { environment } = createMemoryEnvironment('/foo') + environment.startRun() + environment.writeFile( + '/foo/.cta.json', + JSON.stringify({ ...fakeCTAJSON, tailwind: false }, null, 2), + ) + environment.writeFile( + '/foo/package.json', + JSON.stringify( + { + name: 'test', + version: '1.0.0', + scripts: {}, + dependencies: {}, + devDependencies: {}, + }, + null, + 2, + ), + ) + + await addToApp(environment, ['test'], '/foo', { + forced: true, + }) + + const persisted = JSON.parse(await environment.readFile('/foo/.cta.json')) + expect(persisted.tailwind).toBe(true) + }) }) diff --git a/packages/create/tests/blank-preset.test.ts b/packages/create/tests/blank-preset.test.ts new file mode 100644 index 00000000..bdfe6f7e --- /dev/null +++ b/packages/create/tests/blank-preset.test.ts @@ -0,0 +1,287 @@ +import { afterEach, describe, expect, it, vi } from 'vitest' + +import { + finalizeAddOns, + populateAddOnOptionsDefaults, +} from '../src/add-ons.js' +import { createApp } from '../src/create-app.js' +import { createMemoryEnvironment } from '../src/environment.js' +import { createPackageJSON as createEdgePackageJSON } from '../src/edge-package-json.js' +import { createFrameworkDefinition as createReactFrameworkDefinition } from '../src/frameworks/react/index.js' +import { createFrameworkDefinition as createSolidFrameworkDefinition } from '../src/frameworks/solid/index.js' +import { createPackageJSON as createNodePackageJSON } from '../src/package-json.js' + +import type { Framework, FrameworkDefinition, Options } from '../src/types.js' + +const BLANK_FILE_ALLOWLIST = [ + '.cta.json', + '.gitignore', + '.vscode/settings.json', + 'README.md', + 'package.json', + 'src/router.tsx', + 'src/routes/__root.tsx', + 'src/routes/index.tsx', + 'src/styles.css', + 'tsconfig.json', + 'tsr.config.json', + 'vite.config.ts', +] + +const BLANK_DEPENDENCY_ALLOWLIST = [ + '@tanstack/react-router', + '@tanstack/react-start', + 'react', + 'react-dom', +] + +const BLANK_DEV_DEPENDENCY_ALLOWLIST = [ + '@tanstack/router-cli', + '@types/node', + '@types/react', + '@types/react-dom', + '@vitejs/plugin-react', + 'typescript', + 'vite', +] + +function frameworkFromDefinition(definition: FrameworkDefinition): Framework { + const { addOns, base, ...framework } = definition + + return { + ...framework, + getFiles: () => Promise.resolve(Object.keys(base)), + getFileContents: (path: string) => Promise.resolve(base[path]), + getDeletedFiles: () => Promise.resolve([]), + getAddOns: () => addOns, + } +} + +function createBlankOptions(framework: Framework): Options { + return { + projectName: 'blank-app', + targetDir: '/blank-app', + framework, + mode: 'file-router', + typescript: true, + tailwind: false, + packageManager: 'npm', + git: false, + install: false, + intent: false, + chosenAddOns: [], + addOnOptions: {}, + projectPreset: 'blank', + } +} + +afterEach(() => { + vi.unstubAllGlobals() +}) + +describe('blank project preset', () => { + it('renders only the blank file allowlist', async () => { + vi.stubGlobal( + 'fetch', + vi.fn( + async () => + new Response(JSON.stringify({ version: '1.0.0' }), { status: 200 }), + ), + ) + + const framework = frameworkFromDefinition(createReactFrameworkDefinition()) + const options = createBlankOptions(framework) + const { environment, output } = createMemoryEnvironment() + + await createApp(environment, options) + + const files = Object.fromEntries( + Object.entries(output.files).map(([path, contents]) => [ + path.slice(`${options.targetDir}/`.length), + contents, + ]), + ) + + expect(Object.keys(files).sort()).toEqual(BLANK_FILE_ALLOWLIST) + + const config = JSON.parse(files['.cta.json']) + expect(config.projectPreset).toBe('blank') + expect(config.tailwind).toBe(false) + + expect(files['src/routes/__root.tsx']).not.toMatch( + /TanStack(?:Router)?Devtools/, + ) + expect(files['src/router.tsx']).not.toContain( + 'setupRouterSsrQueryIntegration', + ) + expect(files['src/styles.css']).not.toContain('tailwindcss') + expect(files['vite.config.ts']).not.toMatch(/devtools|tailwindcss/) + expect(files['README.md']).not.toMatch(/Tailwind CSS|Vitest/) + }) + + it('uses only blank dependencies and keeps Node and edge composition equal', () => { + const framework = frameworkFromDefinition(createReactFrameworkDefinition()) + const options = createBlankOptions(framework) + + const nodePackageJSON = createNodePackageJSON(options) + const edgePackageJSON = createEdgePackageJSON(options) + + expect(edgePackageJSON).toEqual(nodePackageJSON) + expect(Object.keys(nodePackageJSON.scripts).sort()).toEqual([ + 'build', + 'dev', + 'generate-routes', + 'preview', + ]) + expect(Object.keys(nodePackageJSON.dependencies).sort()).toEqual( + BLANK_DEPENDENCY_ALLOWLIST, + ) + expect(Object.keys(nodePackageJSON.devDependencies).sort()).toEqual( + BLANK_DEV_DEPENDENCY_ALLOWLIST, + ) + }) + + it('keeps the Solid blank preset equally lean', async () => { + vi.stubGlobal( + 'fetch', + vi.fn( + async () => + new Response(JSON.stringify({ version: '1.0.0' }), { status: 200 }), + ), + ) + + const framework = frameworkFromDefinition(createSolidFrameworkDefinition()) + const options = createBlankOptions(framework) + const { environment, output } = createMemoryEnvironment() + + await createApp(environment, options) + + const files = Object.keys(output.files) + .map((path) => path.slice(`${options.targetDir}/`.length)) + .sort() + const packageJSON = createNodePackageJSON(options) + + expect(files).toEqual(BLANK_FILE_ALLOWLIST) + expect(Object.keys(packageJSON.dependencies).sort()).toEqual([ + '@tanstack/solid-router', + '@tanstack/solid-start', + 'solid-js', + ]) + expect(Object.keys(packageJSON.devDependencies).sort()).toEqual([ + '@tanstack/router-cli', + 'typescript', + 'vite', + 'vite-plugin-solid', + ]) + }) + + it('lets an explicit styling add-on opt back into Tailwind', async () => { + vi.stubGlobal( + 'fetch', + vi.fn( + async () => + new Response(JSON.stringify({ version: '1.0.0' }), { status: 200 }), + ), + ) + + const framework = frameworkFromDefinition(createReactFrameworkDefinition()) + const chosenAddOns = await finalizeAddOns(framework, 'file-router', [ + 'shadcn', + ]) + const options = { + ...createBlankOptions(framework), + tailwind: chosenAddOns.some((addOn) => addOn.tailwind === true), + chosenAddOns, + addOnOptions: populateAddOnOptionsDefaults(chosenAddOns), + } + const { environment, output } = createMemoryEnvironment() + + await createApp(environment, options) + + const packageJSON = createNodePackageJSON(options) + expect(options.tailwind).toBe(true) + expect(packageJSON.dependencies).toMatchObject({ + '@tailwindcss/vite': '^4.1.18', + tailwindcss: '^4.1.18', + }) + expect(packageJSON.devDependencies).toMatchObject({ + '@tailwindcss/typography': '^0.5.16', + }) + expect(output.files['/blank-app/src/styles.css']).toContain( + "@plugin '@tailwindcss/typography'", + ) + expect(output.files['/blank-app/vite.config.ts']).toContain('tailwindcss()') + }) + + it('keeps Shopify storefront styling in the blank preset', async () => { + vi.stubGlobal( + 'fetch', + vi.fn( + async () => + new Response(JSON.stringify({ version: '1.0.0' }), { status: 200 }), + ), + ) + + const framework = frameworkFromDefinition(createReactFrameworkDefinition()) + const chosenAddOns = await finalizeAddOns(framework, 'file-router', [ + 'shopify', + ]) + const options = { + ...createBlankOptions(framework), + tailwind: chosenAddOns.some((addOn) => addOn.tailwind === true), + chosenAddOns, + addOnOptions: populateAddOnOptionsDefaults(chosenAddOns), + } + const { environment, output } = createMemoryEnvironment() + + await createApp(environment, options) + + const packageJSON = createNodePackageJSON(options) + expect(options.tailwind).toBe(true) + expect(packageJSON.dependencies).toMatchObject({ + '@tailwindcss/vite': '^4.1.18', + tailwindcss: '^4.1.18', + }) + expect(output.files['/blank-app/src/styles.css']).toContain( + '@import "tailwindcss"', + ) + expect(output.files['/blank-app/vite.config.ts']).toContain('tailwindcss()') + expect(output.files['/blank-app/src/routes/shop.index.tsx']).toContain( + 'tracking-tight', + ) + }) + + it('does not keep Tailwind for a stripped add-on demo', async () => { + vi.stubGlobal( + 'fetch', + vi.fn( + async () => + new Response(JSON.stringify({ version: '1.0.0' }), { status: 200 }), + ), + ) + + const framework = frameworkFromDefinition(createReactFrameworkDefinition()) + const chosenAddOns = await finalizeAddOns(framework, 'file-router', [ + 'posthog', + ]) + const options = { + ...createBlankOptions(framework), + tailwind: chosenAddOns.some((addOn) => addOn.tailwind === true), + chosenAddOns, + } + const { environment, output } = createMemoryEnvironment() + + await createApp(environment, options) + + const packageJSON = createNodePackageJSON(options) + expect(options.tailwind).toBe(false) + expect(packageJSON.dependencies).not.toHaveProperty('@tailwindcss/vite') + expect(packageJSON.dependencies).not.toHaveProperty('tailwindcss') + expect(output.files['/blank-app/src/styles.css']).not.toContain( + 'tailwindcss', + ) + expect(output.files).not.toHaveProperty( + '/blank-app/src/routes/demo/posthog.tsx', + ) + }) +}) diff --git a/packages/create/tests/react-template-dependencies.test.ts b/packages/create/tests/react-template-dependencies.test.ts new file mode 100644 index 00000000..73bbbc9c --- /dev/null +++ b/packages/create/tests/react-template-dependencies.test.ts @@ -0,0 +1,68 @@ +import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs' +import { join } from 'node:path' +import { fileURLToPath } from 'node:url' + +import { describe, expect, it } from 'vitest' + +const reactRoot = fileURLToPath( + new URL('../src/frameworks/react', import.meta.url), +) + +interface PackageManifest { + dependencies?: Record + devDependencies?: Record +} + +function readJson(path: string) { + return JSON.parse(readFileSync(path, 'utf8')) as T +} + +function importsLucide(path: string): boolean { + if (!existsSync(path)) return false + + return readdirSync(path).some((entry) => { + const entryPath = join(path, entry) + if (statSync(entryPath).isDirectory()) return importsLucide(entryPath) + if (!/\.[cm]?[jt]sx?$/.test(entry)) return false + + return /from\s+['"]lucide-react['"]/.test(readFileSync(entryPath, 'utf8')) + }) +} + +function lucideConsumers(group: 'add-ons' | 'examples') { + const groupRoot = join(reactRoot, group) + + return readdirSync(groupRoot) + .map((name) => join(groupRoot, name)) + .filter((path) => statSync(path).isDirectory()) + .filter((path) => importsLucide(join(path, 'assets'))) +} + +describe('React template dependency ownership', () => { + it('does not install Lucide in the base or Tailwind profile', () => { + const base = readJson( + join(reactRoot, 'project/base/package.json'), + ) + const profiles = readJson<{ + tailwindcss?: { dependencies?: Record } + }>(join(reactRoot, 'project/packages.json')) + + expect(base.dependencies?.['lucide-react']).toBeUndefined() + expect(profiles.tailwindcss?.dependencies?.['lucide-react']).toBeUndefined() + }) + + it.each([...lucideConsumers('add-ons'), ...lucideConsumers('examples')])( + '%s declares Lucide when its emitted source imports it', + (consumerPath) => { + const manifest = readJson( + join(consumerPath, 'package.json'), + ) + const dependencies = { + ...manifest.dependencies, + ...manifest.devDependencies, + } + + expect(dependencies['lucide-react']).toBeDefined() + }, + ) +}) From 2ef59259f706a8c9593d6ab351acb8c6c3540e8b Mon Sep 17 00:00:00 2001 From: Tanner Linsley Date: Fri, 17 Jul 2026 11:05:05 -0600 Subject: [PATCH 2/4] fix: migrate skill metadata to spec --- packages/cli/skills/add-addons-existing-app/SKILL.md | 7 ++++--- packages/cli/skills/choose-ecosystem-integrations/SKILL.md | 7 ++++--- packages/cli/skills/create-app-scaffold/SKILL.md | 7 ++++--- .../cli/skills/maintain-custom-addons-dev-watch/SKILL.md | 7 ++++--- packages/cli/skills/query-docs-library-metadata/SKILL.md | 7 ++++--- 5 files changed, 20 insertions(+), 15 deletions(-) diff --git a/packages/cli/skills/add-addons-existing-app/SKILL.md b/packages/cli/skills/add-addons-existing-app/SKILL.md index f11715b3..45226642 100644 --- a/packages/cli/skills/add-addons-existing-app/SKILL.md +++ b/packages/cli/skills/add-addons-existing-app/SKILL.md @@ -4,9 +4,10 @@ description: > Apply integrations to existing projects with tanstack add, including add-on id resolution, dependency chains, option prompts, and .cta.json project metadata preconditions. -type: core -library: tanstack-cli -library_version: "0.62.1" +metadata: + type: core + library: tanstack-cli + library_version: "0.62.1" --- # Add Add-ons To Existing App diff --git a/packages/cli/skills/choose-ecosystem-integrations/SKILL.md b/packages/cli/skills/choose-ecosystem-integrations/SKILL.md index 4f2ebb26..4cd53d20 100644 --- a/packages/cli/skills/choose-ecosystem-integrations/SKILL.md +++ b/packages/cli/skills/choose-ecosystem-integrations/SKILL.md @@ -5,9 +5,10 @@ description: > tanstack ecosystem --json, tanstack create --list-add-ons --json, and tanstack create --addon-details --json. Covers exclusive categories, provider options, and router-only compatibility constraints. -type: composition -library: tanstack-cli -library_version: "0.62.1" +metadata: + type: composition + library: tanstack-cli + library_version: "0.62.1" requires: - create-app-scaffold - query-docs-library-metadata diff --git a/packages/cli/skills/create-app-scaffold/SKILL.md b/packages/cli/skills/create-app-scaffold/SKILL.md index 30a1d330..8a3df44a 100644 --- a/packages/cli/skills/create-app-scaffold/SKILL.md +++ b/packages/cli/skills/create-app-scaffold/SKILL.md @@ -5,9 +5,10 @@ description: > --toolchain, --deployment, --add-ons, --blank, and --router-only. Covers flag compatibility, non-interactive defaults, and intent-preserving command construction. -type: core -library: tanstack-cli -library_version: "0.70.0" +metadata: + type: core + library: tanstack-cli + library_version: "0.70.0" --- # Create App Scaffold diff --git a/packages/cli/skills/maintain-custom-addons-dev-watch/SKILL.md b/packages/cli/skills/maintain-custom-addons-dev-watch/SKILL.md index 4ef5fac4..fdda13eb 100644 --- a/packages/cli/skills/maintain-custom-addons-dev-watch/SKILL.md +++ b/packages/cli/skills/maintain-custom-addons-dev-watch/SKILL.md @@ -4,9 +4,10 @@ description: > Build and iterate custom add-ons/templates with tanstack add-on init, add-on compile, add-on dev, and tanstack create --dev-watch, including sync loop preconditions, watch-path validation, and project metadata constraints. -type: lifecycle -library: tanstack-cli -library_version: "0.62.1" +metadata: + type: lifecycle + library: tanstack-cli + library_version: "0.62.1" requires: - add-addons-existing-app --- diff --git a/packages/cli/skills/query-docs-library-metadata/SKILL.md b/packages/cli/skills/query-docs-library-metadata/SKILL.md index 30216bf1..531e0688 100644 --- a/packages/cli/skills/query-docs-library-metadata/SKILL.md +++ b/packages/cli/skills/query-docs-library-metadata/SKILL.md @@ -4,9 +4,10 @@ description: > Retrieve machine-readable context with tanstack libraries, tanstack doc, tanstack search-docs, tanstack create --list-add-ons --json, and --addon-details for agent-safe discovery and preflight validation. -type: core -library: tanstack-cli -library_version: "0.62.1" +metadata: + type: core + library: tanstack-cli + library_version: "0.62.1" --- # Query Docs And Library Metadata From a12ba9f82e20ba062352bc3bfb5b4889c0c2987d Mon Sep 17 00:00:00 2001 From: Tanner Linsley Date: Fri, 17 Jul 2026 11:27:15 -0600 Subject: [PATCH 3/4] fix: address blank preset review findings --- .../cli/skills/create-app-scaffold/SKILL.md | 2 +- packages/cli/src/cli.ts | 22 ++++-- packages/cli/tests-e2e/blank-smoke.spec.ts | 61 +++++++++++++++- packages/cli/tests-e2e/helpers.ts | 73 ++++++++++--------- .../react/project/base/README.md.ejs | 2 +- .../solid/project/base/README.md.ejs | 2 +- packages/create/tests/blank-preset.test.ts | 32 ++++++++ packages/create/tests/package-json.test.ts | 59 +++++++++++++++ 8 files changed, 207 insertions(+), 46 deletions(-) diff --git a/packages/cli/skills/create-app-scaffold/SKILL.md b/packages/cli/skills/create-app-scaffold/SKILL.md index 8a3df44a..a0162ce9 100644 --- a/packages/cli/skills/create-app-scaffold/SKILL.md +++ b/packages/cli/skills/create-app-scaffold/SKILL.md @@ -90,7 +90,7 @@ In non-interactive runs, empty add-on selection can complete with defaults and s Source: https://github.com/TanStack/cli/issues/234 -### HIGH Use --no-tailwind when a minimal scaffold is required +### HIGH Avoid --no-tailwind; use --blank for a minimal scaffold Wrong: ```bash diff --git a/packages/cli/src/cli.ts b/packages/cli/src/cli.ts index e60c5e72..ecd8bb4e 100644 --- a/packages/cli/src/cli.ts +++ b/packages/cli/src/cli.ts @@ -633,6 +633,17 @@ export function cli({ addHiddenAgentFlag(program) + function validateCreateCommandOptions(options: CliOptions) { + const legacyCreateFlags = validateLegacyCreateFlags(options) + if (legacyCreateFlags.error) { + throw new Error(legacyCreateFlags.error) + } + + for (const warning of legacyCreateFlags.warnings) { + log.warn(warning) + } + } + // Helper to create the create command action handler async function handleCreate(projectName: string, options: CliOptions) { try { @@ -643,14 +654,7 @@ export function cli({ properties: getCreateTelemetryProperties(projectName, options), }, async (telemetry) => { - const legacyCreateFlags = validateLegacyCreateFlags(options) - if (legacyCreateFlags.error) { - throw new Error(legacyCreateFlags.error) - } - - for (const warning of legacyCreateFlags.warnings) { - log.warn(warning) - } + validateCreateCommandOptions(options) if (options.listAddOns) { const addOns = await getAllAddOns( @@ -1118,6 +1122,8 @@ export function cli({ }, }, async () => { + validateCreateCommandOptions(options) + const frameworkName = options.framework || defaultFramework || 'React' const framework = getFrameworkByName(frameworkName) if (!framework) { diff --git a/packages/cli/tests-e2e/blank-smoke.spec.ts b/packages/cli/tests-e2e/blank-smoke.spec.ts index 677f3d8f..e3f5635a 100644 --- a/packages/cli/tests-e2e/blank-smoke.spec.ts +++ b/packages/cli/tests-e2e/blank-smoke.spec.ts @@ -9,7 +9,7 @@ import { writeFile, } from 'node:fs/promises' import { tmpdir } from 'node:os' -import { join, relative } from 'node:path' +import { dirname, join, relative } from 'node:path' import { promisify } from 'node:util' import { expect, test } from '@playwright/test' @@ -101,6 +101,65 @@ test('@blocking refuses a non-empty target in non-interactive mode', async () => } }) +test('@blocking rejects conflicting blank flags for dev', async () => { + const rootDir = await mkdtemp(join(tmpdir(), 'tanstack-blank-dev-conflict-')) + const appName = 'invalid-blank-dev-app' + + try { + let failure: unknown + try { + await execFileAsync( + process.execPath, + [ + getRepoPath('packages/cli/dist/index.js'), + 'dev', + appName, + '--blank', + '--tailwind', + '-y', + '--no-install', + '--no-git', + ], + { cwd: rootDir, encoding: 'utf8' }, + ) + } catch (error) { + failure = error + } + + const commandError = failure as + | (Error & { code?: number; stdout?: string; stderr?: string }) + | undefined + expect(commandError?.code).toBe(1) + expect( + `${commandError?.stdout ?? ''}\n${commandError?.stderr ?? ''}`, + ).toContain('--blank cannot be combined with --tailwind') + await expect(access(join(rootDir, appName))).rejects.toThrow() + } finally { + await rm(rootDir, { recursive: true, force: true }) + } +}) + +test('@blocking cleans up a fixture when setup fails', async () => { + let failedRootDir: string | undefined + + await expect( + createReactAppFixture({ + appName: 'failed-blank-fixture', + blank: true, + packageManager: 'pnpm', + skipDevServer: true, + afterCreate: async (appDir) => { + failedRootDir = dirname(appDir) + await access(join(appDir, 'node_modules')) + throw new Error('fixture setup sentinel') + }, + }), + ).rejects.toThrow('fixture setup sentinel') + + expect(failedRootDir).toBeDefined() + await expect(access(failedRootDir!)).rejects.toThrow() +}) + test('@blocking creates a production-valid blank React app', async ({ page, }) => { diff --git a/packages/cli/tests-e2e/helpers.ts b/packages/cli/tests-e2e/helpers.ts index f5883b9b..091ed028 100644 --- a/packages/cli/tests-e2e/helpers.ts +++ b/packages/cli/tests-e2e/helpers.ts @@ -365,6 +365,9 @@ export async function createAppFixture( routerOnly = false, } = options const appDir = join(rootDir, appName) + const cleanup = async () => { + await rm(rootDir, { recursive: true, force: true }) + } const createArgs = [ cliDistPath, @@ -397,41 +400,46 @@ export async function createAppFixture( createArgs.push('--add-ons', addOns.join(',')) } - const createStartedAt = now() - await runCommand( - 'node', - createArgs, - { - cwd: rootDir, - env: { - CI: '1', + try { + const createStartedAt = now() + await runCommand( + 'node', + createArgs, + { + cwd: rootDir, + env: { + CI: '1', + }, }, - }, - ) - mark('create', createStartedAt) + ) + mark('create', createStartedAt) - await afterCreate?.(appDir) + await afterCreate?.(appDir) - await patchViteConfigForE2E(appDir) + await patchViteConfigForE2E(appDir) - if (postCreateAddOns?.length) { - const postAddOnsStartedAt = now() - await runCommand('node', [cliDistPath, 'add', ...postCreateAddOns], { - cwd: appDir, - env: { - CI: '1', - }, - }) + if (postCreateAddOns?.length) { + const postAddOnsStartedAt = now() + await runCommand('node', [cliDistPath, 'add', ...postCreateAddOns], { + cwd: appDir, + env: { + CI: '1', + }, + }) - mark('post-add-ons', postAddOnsStartedAt) + mark('post-add-ons', postAddOnsStartedAt) - await patchViteConfigForE2E(appDir) - } + await patchViteConfigForE2E(appDir) + } - if (runQualityGatesChecks) { - const qualityGatesStartedAt = now() - await runQualityGates(appDir, packageManager) - mark('quality-gates', qualityGatesStartedAt) + if (runQualityGatesChecks) { + const qualityGatesStartedAt = now() + await runQualityGates(appDir, packageManager) + mark('quality-gates', qualityGatesStartedAt) + } + } catch (error) { + await cleanup().catch(() => undefined) + throw error } if (skipDevServer) { @@ -443,9 +451,7 @@ export async function createAppFixture( framework, packageManager, stop: async () => {}, - cleanup: async () => { - await rm(rootDir, { recursive: true, force: true }) - }, + cleanup, } } @@ -479,6 +485,7 @@ export async function createAppFixture( mark('dev-server-ready', devServerStartedAt) } catch (error) { await stopChild(server) + await cleanup().catch(() => undefined) throw new Error( `Failed to start app server at ${url}\nstdout:\n${serverStdout}\n\nstderr:\n${serverStderr}\n\n${error}`, ) @@ -495,9 +502,7 @@ export async function createAppFixture( stop: async () => { await stopChild(server) }, - cleanup: async () => { - await rm(rootDir, { recursive: true, force: true }) - }, + cleanup, } } diff --git a/packages/create/src/frameworks/react/project/base/README.md.ejs b/packages/create/src/frameworks/react/project/base/README.md.ejs index d8c38408..b1e1d980 100644 --- a/packages/create/src/frameworks/react/project/base/README.md.ejs +++ b/packages/create/src/frameworks/react/project/base/README.md.ejs @@ -1,7 +1,7 @@ <% if (blank) { -%> # <%= projectName %> -A minimal TanStack Start app with one route and plain CSS. +A minimal TanStack Start app with one route and <%= tailwind ? 'Tailwind CSS' : 'plain CSS' %>. ```bash <%= packageManager %> install diff --git a/packages/create/src/frameworks/solid/project/base/README.md.ejs b/packages/create/src/frameworks/solid/project/base/README.md.ejs index db1dc56f..f45a0219 100644 --- a/packages/create/src/frameworks/solid/project/base/README.md.ejs +++ b/packages/create/src/frameworks/solid/project/base/README.md.ejs @@ -1,7 +1,7 @@ <% if (blank) { -%> # <%= projectName %> -A minimal TanStack Start app with one route and plain CSS. +A minimal TanStack Start app with one route and <%= tailwind ? 'Tailwind CSS' : 'plain CSS' %>. ```bash <%= packageManager %> install diff --git a/packages/create/tests/blank-preset.test.ts b/packages/create/tests/blank-preset.test.ts index bdfe6f7e..22909adf 100644 --- a/packages/create/tests/blank-preset.test.ts +++ b/packages/create/tests/blank-preset.test.ts @@ -117,6 +117,7 @@ describe('blank project preset', () => { expect(files['src/styles.css']).not.toContain('tailwindcss') expect(files['vite.config.ts']).not.toMatch(/devtools|tailwindcss/) expect(files['README.md']).not.toMatch(/Tailwind CSS|Vitest/) + expect(files['README.md']).toContain('plain CSS') }) it('uses only blank dependencies and keeps Node and edge composition equal', () => { @@ -173,6 +174,7 @@ describe('blank project preset', () => { 'vite', 'vite-plugin-solid', ]) + expect(output.files['/blank-app/README.md']).toContain('plain CSS') }) it('lets an explicit styling add-on opt back into Tailwind', async () => { @@ -211,6 +213,36 @@ describe('blank project preset', () => { "@plugin '@tailwindcss/typography'", ) expect(output.files['/blank-app/vite.config.ts']).toContain('tailwindcss()') + expect(output.files['/blank-app/README.md']).toContain('Tailwind CSS') + expect(output.files['/blank-app/README.md']).not.toContain('plain CSS') + }) + + it('describes Tailwind accurately for a Solid styling add-on', async () => { + vi.stubGlobal( + 'fetch', + vi.fn( + async () => + new Response(JSON.stringify({ version: '1.0.0' }), { status: 200 }), + ), + ) + + const framework = frameworkFromDefinition(createSolidFrameworkDefinition()) + const chosenAddOns = await finalizeAddOns(framework, 'file-router', [ + 'solid-ui', + ]) + const options = { + ...createBlankOptions(framework), + tailwind: chosenAddOns.some((addOn) => addOn.tailwind === true), + chosenAddOns, + addOnOptions: populateAddOnOptionsDefaults(chosenAddOns), + } + const { environment, output } = createMemoryEnvironment() + + await createApp(environment, options) + + expect(options.tailwind).toBe(true) + expect(output.files['/blank-app/README.md']).toContain('Tailwind CSS') + expect(output.files['/blank-app/README.md']).not.toContain('plain CSS') }) it('keeps Shopify storefront styling in the blank preset', async () => { diff --git a/packages/create/tests/package-json.test.ts b/packages/create/tests/package-json.test.ts index f9e05306..692c3f9f 100644 --- a/packages/create/tests/package-json.test.ts +++ b/packages/create/tests/package-json.test.ts @@ -1,10 +1,69 @@ import { describe, expect, it } from 'vitest' +import { createPackageJSON as createEdgePackageJSON } from '../src/edge-package-json.js' import { createPackageJSON } from '../src/package-json.js' import type { Options, Framework } from '../src/types.js' describe('createPackageJSON', () => { + it.each([ + ['react', '@tanstack/react-start'], + ['solid', '@tanstack/solid-start'], + ])( + 'keeps the router plugin scoped to %s router-only projects', + (frameworkId, startPackage) => { + const framework = { + id: frameworkId, + basePackageJSON: { + dependencies: { + [startPackage]: 'latest', + }, + }, + optionalPackages: { + typescript: {}, + devtools: {}, + examples: {}, + tailwindcss: {}, + 'file-router': { + devDependencies: { + '@tanstack/router-cli': 'latest', + }, + }, + }, + } as unknown as Framework + const options = { + chosenAddOns: [], + addOnOptions: {}, + mode: 'file-router', + typescript: true, + tailwind: false, + projectName: 'test', + packageManager: 'pnpm', + framework, + } as unknown as Options + + const startPackageJSON = createPackageJSON(options) + expect(startPackageJSON.devDependencies).not.toHaveProperty( + '@tanstack/router-plugin', + ) + + const routerOnlyOptions = { + ...options, + routerOnly: true, + } + const routerOnlyPackageJSON = createPackageJSON(routerOnlyOptions) + + expect(routerOnlyPackageJSON.dependencies).not.toHaveProperty(startPackage) + expect(routerOnlyPackageJSON.devDependencies).toHaveProperty( + '@tanstack/router-plugin', + 'latest', + ) + expect(createEdgePackageJSON(routerOnlyOptions)).toEqual( + routerOnlyPackageJSON, + ) + }, + ) + it('should create a package.json', () => { const packageJSON = createPackageJSON({ chosenAddOns: [ From 8711a8b4428722a417960050871a014b3a9bc839 Mon Sep 17 00:00:00 2001 From: Tanner Linsley Date: Fri, 17 Jul 2026 11:32:21 -0600 Subject: [PATCH 4/4] test: assert Start dependency scope --- packages/create/tests/package-json.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/create/tests/package-json.test.ts b/packages/create/tests/package-json.test.ts index 692c3f9f..e2d96a06 100644 --- a/packages/create/tests/package-json.test.ts +++ b/packages/create/tests/package-json.test.ts @@ -43,6 +43,7 @@ describe('createPackageJSON', () => { } as unknown as Options const startPackageJSON = createPackageJSON(options) + expect(startPackageJSON.dependencies).toHaveProperty(startPackage, 'latest') expect(startPackageJSON.devDependencies).not.toHaveProperty( '@tanstack/router-plugin', )