diff --git a/packages/create/src/create-app.ts b/packages/create/src/create-app.ts index c2ab351a..8318e889 100644 --- a/packages/create/src/create-app.ts +++ b/packages/create/src/create-app.ts @@ -6,6 +6,7 @@ import { writeConfigFileToEnvironment } from './config-file.js' import { getPackageManagerScriptCommand, packageManagerInstall, + translateExecuteCommand, } from './package-manager.js' import { createPackageJSON } from './package-json.js' import { createTemplateFile } from './template-file.js' @@ -181,18 +182,19 @@ async function runCommandsAndInstallDependencies( addOn.phase === phase && addOn.command && addOn.command.command, )) { s.start(`Running commands for ${addOn.name}...`) - const cmd = formatCommand({ + const translated = translateExecuteCommand(options.packageManager, { command: addOn.command!.command, args: addOn.command!.args || [], }) + const cmd = formatCommand(translated) environment.startStep({ id: 'run-commands', type: 'command', message: cmd, }) await environment.execute( - addOn.command!.command, - addOn.command!.args || [], + translated.command, + translated.args, options.targetDir, { inherit: true }, ) @@ -208,10 +210,11 @@ async function runCommandsAndInstallDependencies( options.starter.command.command ) { s.start(`Setting up starter ${options.starter.name}...`) - const cmd = formatCommand({ + const starterTranslated = translateExecuteCommand(options.packageManager, { command: options.starter.command.command, args: options.starter.command.args || [], }) + const cmd = formatCommand(starterTranslated) environment.startStep({ id: 'run-starter-command', type: 'command', @@ -219,8 +222,8 @@ async function runCommandsAndInstallDependencies( }) await environment.execute( - options.starter.command.command, - options.starter.command.args || [], + starterTranslated.command, + starterTranslated.args, options.targetDir, { inherit: true }, ) diff --git a/packages/create/src/frameworks.ts b/packages/create/src/frameworks.ts index 759e3560..8da8f54c 100644 --- a/packages/create/src/frameworks.ts +++ b/packages/create/src/frameworks.ts @@ -72,8 +72,15 @@ export function scanAddOnDirectories(addOnsDirectories: Array) { } let readme: string | undefined + let readmeIsEjs = false if (existsSync(resolve(addOnsBase, dir, 'README.md'))) { readme = readFileSync(resolve(addOnsBase, dir, 'README.md'), 'utf-8') + } else if (existsSync(resolve(addOnsBase, dir, 'README.md.ejs'))) { + readme = readFileSync( + resolve(addOnsBase, dir, 'README.md.ejs'), + 'utf-8', + ) + readmeIsEjs = true } let smallLogo: string | undefined @@ -107,6 +114,7 @@ export function scanAddOnDirectories(addOnsDirectories: Array) { packageAdditions, packageTemplate, readme, + readmeIsEjs, files, smallLogo, getFiles, diff --git a/packages/create/src/frameworks/react/add-ons/better-auth/README.md b/packages/create/src/frameworks/react/add-ons/better-auth/README.md.ejs similarity index 82% rename from packages/create/src/frameworks/react/add-ons/better-auth/README.md rename to packages/create/src/frameworks/react/add-ons/better-auth/README.md.ejs index 3676c31a..1eedc56d 100644 --- a/packages/create/src/frameworks/react/add-ons/better-auth/README.md +++ b/packages/create/src/frameworks/react/add-ons/better-auth/README.md.ejs @@ -3,7 +3,7 @@ 1. Generate and set the `BETTER_AUTH_SECRET` environment variable in your `.env.local`: ```bash - npx @better-auth/cli secret + <%- getPackageManagerExecuteScript('@better-auth/cli', ['secret']) %> ``` 2. Visit the [Better Auth documentation](https://www.better-auth.com) to unlock the full potential of authentication in your app. @@ -28,5 +28,5 @@ export const auth = betterAuth({ Then run migrations: ```bash -npx @better-auth/cli migrate +<%- getPackageManagerExecuteScript('@better-auth/cli', ['migrate']) %> ``` diff --git a/packages/create/src/frameworks/react/add-ons/better-auth/assets/_dot_env.local.append b/packages/create/src/frameworks/react/add-ons/better-auth/assets/_dot_env.local.append deleted file mode 100644 index 408cfc28..00000000 --- a/packages/create/src/frameworks/react/add-ons/better-auth/assets/_dot_env.local.append +++ /dev/null @@ -1,3 +0,0 @@ -# Better Auth configuration -BETTER_AUTH_URL=http://localhost:3000 -BETTER_AUTH_SECRET= # Generate a secret key: `npx @better-auth/cli secret` diff --git a/packages/create/src/frameworks/react/add-ons/better-auth/assets/_dot_env.local.append.ejs b/packages/create/src/frameworks/react/add-ons/better-auth/assets/_dot_env.local.append.ejs new file mode 100644 index 00000000..24a30544 --- /dev/null +++ b/packages/create/src/frameworks/react/add-ons/better-auth/assets/_dot_env.local.append.ejs @@ -0,0 +1,3 @@ +# Better Auth configuration +BETTER_AUTH_URL=http://localhost:3000 +BETTER_AUTH_SECRET= # Generate a secret key: `<%- getPackageManagerExecuteScript('@better-auth/cli', ['secret']) %>` diff --git a/packages/create/src/frameworks/react/add-ons/convex/README.md b/packages/create/src/frameworks/react/add-ons/convex/README.md deleted file mode 100644 index 44c80c8b..00000000 --- a/packages/create/src/frameworks/react/add-ons/convex/README.md +++ /dev/null @@ -1,4 +0,0 @@ -## Setting up Convex - -- Set the `VITE_CONVEX_URL` and `CONVEX_DEPLOYMENT` environment variables in your `.env.local`. (Or run `npx convex init` to set them automatically.) -- Run `npx convex dev` to start the Convex server. diff --git a/packages/create/src/frameworks/react/add-ons/convex/README.md.ejs b/packages/create/src/frameworks/react/add-ons/convex/README.md.ejs new file mode 100644 index 00000000..ebd35c7b --- /dev/null +++ b/packages/create/src/frameworks/react/add-ons/convex/README.md.ejs @@ -0,0 +1,4 @@ +## Setting up Convex + +- Set the `VITE_CONVEX_URL` and `CONVEX_DEPLOYMENT` environment variables in your `.env.local`. (Or run `<%- getPackageManagerExecuteScript('convex', ['init']) %>` to set them automatically.) +- Run `<%- getPackageManagerExecuteScript('convex', ['dev']) %>` to start the Convex server. diff --git a/packages/create/src/frameworks/react/add-ons/drizzle/assets/src/routes/demo/drizzle.tsx b/packages/create/src/frameworks/react/add-ons/drizzle/assets/src/routes/demo/drizzle.tsx.ejs similarity index 96% rename from packages/create/src/frameworks/react/add-ons/drizzle/assets/src/routes/demo/drizzle.tsx rename to packages/create/src/frameworks/react/add-ons/drizzle/assets/src/routes/demo/drizzle.tsx.ejs index 6e0719d2..67db3d58 100644 --- a/packages/create/src/frameworks/react/add-ons/drizzle/assets/src/routes/demo/drizzle.tsx +++ b/packages/create/src/frameworks/react/add-ons/drizzle/assets/src/routes/demo/drizzle.tsx.ejs @@ -163,19 +163,19 @@ function DemoDrizzle() {
  • Run:{' '} - npx drizzle-kit generate + <%- getPackageManagerExecuteScript('drizzle-kit', ['generate']) %>
  • Run:{' '} - npx drizzle-kit migrate + <%- getPackageManagerExecuteScript('drizzle-kit', ['migrate']) %>
  • Optional:{' '} - npx drizzle-kit studio + <%- getPackageManagerExecuteScript('drizzle-kit', ['studio']) %>
  • diff --git a/packages/create/src/frameworks/react/add-ons/prisma/assets/src/routes/demo/prisma.tsx b/packages/create/src/frameworks/react/add-ons/prisma/assets/src/routes/demo/prisma.tsx.ejs similarity index 96% rename from packages/create/src/frameworks/react/add-ons/prisma/assets/src/routes/demo/prisma.tsx rename to packages/create/src/frameworks/react/add-ons/prisma/assets/src/routes/demo/prisma.tsx.ejs index f39942cf..05f5c08c 100644 --- a/packages/create/src/frameworks/react/add-ons/prisma/assets/src/routes/demo/prisma.tsx +++ b/packages/create/src/frameworks/react/add-ons/prisma/assets/src/routes/demo/prisma.tsx.ejs @@ -162,19 +162,19 @@ function DemoPrisma() {
  • Run:{' '} - npx prisma generate + <%- getPackageManagerExecuteScript('prisma', ['generate']) %>
  • Run:{' '} - npx prisma db push + <%- getPackageManagerExecuteScript('prisma', ['db', 'push']) %>
  • Optional:{' '} - npx prisma studio + <%- getPackageManagerExecuteScript('prisma', ['studio']) %>
  • diff --git a/packages/create/src/frameworks/react/add-ons/prisma/package.json.ejs b/packages/create/src/frameworks/react/add-ons/prisma/package.json.ejs index 52a32dfa..1c8af8ea 100644 --- a/packages/create/src/frameworks/react/add-ons/prisma/package.json.ejs +++ b/packages/create/src/frameworks/react/add-ons/prisma/package.json.ejs @@ -11,7 +11,7 @@ "tsx": "^4.20.6" }, "scripts": {<% if (addOnOption.prisma.database === 'postgres') { %> - "post-cta-init": "npx create-db@latest --user-agent tanstack/tsrouter",<% } %> + "post-cta-init": "<%- getPackageManagerExecuteScript('create-db@latest', ['--user-agent', 'tanstack/tsrouter']) %>",<% } %> "db:generate": "dotenv -e .env.local -- prisma generate", "db:push": "dotenv -e .env.local -- prisma db push", "db:migrate": "dotenv -e .env.local -- prisma migrate dev", 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 cbc233bd..ebdcd6aa 100644 --- a/packages/create/src/frameworks/react/project/base/README.md.ejs +++ b/packages/create/src/frameworks/react/project/base/README.md.ejs @@ -52,7 +52,7 @@ This project uses [eslint](https://eslint.org/) and [prettier](https://prettier. ``` <% } %> <% for(const addon of addOns.filter(addon => addon.readme)) { %> -<%- addon.readme %> +<%- addon.readmeIsEjs ? renderTemplate(addon.readme) : addon.readme %> <% } %> ## Routing diff --git a/packages/create/src/frameworks/solid/add-ons/better-auth/README.md b/packages/create/src/frameworks/solid/add-ons/better-auth/README.md.ejs similarity index 82% rename from packages/create/src/frameworks/solid/add-ons/better-auth/README.md rename to packages/create/src/frameworks/solid/add-ons/better-auth/README.md.ejs index 3676c31a..1eedc56d 100644 --- a/packages/create/src/frameworks/solid/add-ons/better-auth/README.md +++ b/packages/create/src/frameworks/solid/add-ons/better-auth/README.md.ejs @@ -3,7 +3,7 @@ 1. Generate and set the `BETTER_AUTH_SECRET` environment variable in your `.env.local`: ```bash - npx @better-auth/cli secret + <%- getPackageManagerExecuteScript('@better-auth/cli', ['secret']) %> ``` 2. Visit the [Better Auth documentation](https://www.better-auth.com) to unlock the full potential of authentication in your app. @@ -28,5 +28,5 @@ export const auth = betterAuth({ Then run migrations: ```bash -npx @better-auth/cli migrate +<%- getPackageManagerExecuteScript('@better-auth/cli', ['migrate']) %> ``` diff --git a/packages/create/src/frameworks/solid/add-ons/better-auth/assets/_dot_env.local.append b/packages/create/src/frameworks/solid/add-ons/better-auth/assets/_dot_env.local.append deleted file mode 100644 index 408cfc28..00000000 --- a/packages/create/src/frameworks/solid/add-ons/better-auth/assets/_dot_env.local.append +++ /dev/null @@ -1,3 +0,0 @@ -# Better Auth configuration -BETTER_AUTH_URL=http://localhost:3000 -BETTER_AUTH_SECRET= # Generate a secret key: `npx @better-auth/cli secret` diff --git a/packages/create/src/frameworks/solid/add-ons/better-auth/assets/_dot_env.local.append.ejs b/packages/create/src/frameworks/solid/add-ons/better-auth/assets/_dot_env.local.append.ejs new file mode 100644 index 00000000..24a30544 --- /dev/null +++ b/packages/create/src/frameworks/solid/add-ons/better-auth/assets/_dot_env.local.append.ejs @@ -0,0 +1,3 @@ +# Better Auth configuration +BETTER_AUTH_URL=http://localhost:3000 +BETTER_AUTH_SECRET= # Generate a secret key: `<%- getPackageManagerExecuteScript('@better-auth/cli', ['secret']) %>` diff --git a/packages/create/src/frameworks/solid/add-ons/convex/README.md b/packages/create/src/frameworks/solid/add-ons/convex/README.md deleted file mode 100644 index 44c80c8b..00000000 --- a/packages/create/src/frameworks/solid/add-ons/convex/README.md +++ /dev/null @@ -1,4 +0,0 @@ -## Setting up Convex - -- Set the `VITE_CONVEX_URL` and `CONVEX_DEPLOYMENT` environment variables in your `.env.local`. (Or run `npx convex init` to set them automatically.) -- Run `npx convex dev` to start the Convex server. diff --git a/packages/create/src/frameworks/solid/add-ons/convex/README.md.ejs b/packages/create/src/frameworks/solid/add-ons/convex/README.md.ejs new file mode 100644 index 00000000..ebd35c7b --- /dev/null +++ b/packages/create/src/frameworks/solid/add-ons/convex/README.md.ejs @@ -0,0 +1,4 @@ +## Setting up Convex + +- Set the `VITE_CONVEX_URL` and `CONVEX_DEPLOYMENT` environment variables in your `.env.local`. (Or run `<%- getPackageManagerExecuteScript('convex', ['init']) %>` to set them automatically.) +- Run `<%- getPackageManagerExecuteScript('convex', ['dev']) %>` to start the Convex server. diff --git a/packages/create/src/frameworks/solid/add-ons/solid-ui/README.md b/packages/create/src/frameworks/solid/add-ons/solid-ui/README.md.ejs similarity index 72% rename from packages/create/src/frameworks/solid/add-ons/solid-ui/README.md rename to packages/create/src/frameworks/solid/add-ons/solid-ui/README.md.ejs index cb8c695b..0257b1d1 100644 --- a/packages/create/src/frameworks/solid/add-ons/solid-ui/README.md +++ b/packages/create/src/frameworks/solid/add-ons/solid-ui/README.md.ejs @@ -5,5 +5,5 @@ This installation of Solid-UI follows the manual instructions but was modified t To install the components, run the following command (this install button): ```bash -npx solidui-cli@latest add button +<%- getPackageManagerExecuteScript('solidui-cli@latest', ['add', 'button']) %> ``` 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 3f68cfb5..c4aabe61 100644 --- a/packages/create/src/frameworks/solid/project/base/README.md.ejs +++ b/packages/create/src/frameworks/solid/project/base/README.md.ejs @@ -31,7 +31,7 @@ If you prefer not to use Tailwind CSS: 4. Uninstall the packages: `<%= getPackageManagerAddScript('@tailwindcss/vite tailwindcss', true) %>` <% for(const addon of addOns.filter(addon => addon.readme)) { %> -<%- addon.readme %> +<%- addon.readmeIsEjs ? renderTemplate(addon.readme) : addon.readme %> <% } %> ## Routing diff --git a/packages/create/src/package-manager.ts b/packages/create/src/package-manager.ts index faa018f4..59739e7a 100644 --- a/packages/create/src/package-manager.ts +++ b/packages/create/src/package-manager.ts @@ -101,6 +101,43 @@ export function packageManagerInstall( return environment.execute(command, commandArgs, cwd) } +export function translateExecuteCommand( + packageManager: PackageManager, + command: { command: string; args?: Array }, +): { command: string; args: Array } { + const args = command.args || [] + const parsed = parseExecuteCommand(command.command, args) + if (parsed) { + return getPackageManagerExecuteCommand(packageManager, parsed.pkg, parsed.args) + } + return { command: command.command, args } +} + +function parseExecuteCommand( + command: string, + args: Array, +): { pkg: string; args: Array } | null { + if (command === 'npx') { + const filtered = args[0] === '-y' ? args.slice(1) : args + const [pkg, ...rest] = filtered + return pkg ? { pkg, args: rest } : null + } + if (command === 'pnpx' || command === 'bunx') { + const filtered = command === 'bunx' && args[0] === '--bun' ? args.slice(1) : args + const [pkg, ...rest] = filtered + return pkg ? { pkg, args: rest } : null + } + if ((command === 'pnpm' || command === 'yarn') && args[0] === 'dlx') { + const [, pkg, ...rest] = args + return pkg ? { pkg, args: rest } : null + } + if (command === 'deno' && args[0] === 'run' && args[1]?.startsWith('npm:')) { + const pkg = args[1].slice(4) + return pkg ? { pkg, args: args.slice(2) } : null + } + return null +} + export function packageManagerExecute( environment: Environment, cwd: string, diff --git a/packages/create/src/template-file.ts b/packages/create/src/template-file.ts index 65c9083b..6d6226a7 100644 --- a/packages/create/src/template-file.ts +++ b/packages/create/src/template-file.ts @@ -4,6 +4,7 @@ import { format } from 'prettier' import { formatCommand } from './utils.js' import { + getPackageManagerExecuteCommand, getPackageManagerInstallCommand, getPackageManagerScriptCommand, } from './package-manager.js' @@ -48,6 +49,14 @@ export function createTemplateFile(environment: Environment, options: Options) { ]), ) } + function getPackageManagerExecuteScript( + pkg: string, + args: Array = [], + ) { + return formatCommand( + getPackageManagerExecuteCommand(options.packageManager, pkg, args), + ) + } class IgnoreFileError extends Error { constructor() { @@ -113,6 +122,7 @@ export function createTemplateFile(environment: Environment, options: Options) { getPackageManagerAddScript, getPackageManagerRunScript, + getPackageManagerExecuteScript, relativePath: (path: string, stripExtension: boolean = false) => relativePath(file, path, stripExtension), @@ -120,6 +130,10 @@ export function createTemplateFile(environment: Environment, options: Options) { integrationImportContent, integrationImportCode, + renderTemplate: (content: string) => { + return render(content, templateValues) + }, + ignoreFile: () => { throw new IgnoreFileError() }, diff --git a/packages/create/src/types.ts b/packages/create/src/types.ts index 6a216925..737183f7 100644 --- a/packages/create/src/types.ts +++ b/packages/create/src/types.ts @@ -119,6 +119,7 @@ export const AddOnInfoSchema = AddOnBaseSchema.extend({ integrations: z.array(IntegrationSchema).optional(), phase: z.enum(['setup', 'add-on']), readme: z.string().optional(), + readmeIsEjs: z.boolean().optional(), }) export const AddOnCompiledSchema = AddOnInfoSchema.extend({ diff --git a/packages/create/tests/package-manager.test.ts b/packages/create/tests/package-manager.test.ts index b247f637..a69a48c8 100644 --- a/packages/create/tests/package-manager.test.ts +++ b/packages/create/tests/package-manager.test.ts @@ -4,6 +4,7 @@ import { getPackageManagerExecuteCommand, getPackageManagerInstallCommand, getPackageManagerScriptCommand, + translateExecuteCommand, } from '../src/package-manager.js' import { formatCommand } from '../src/utils.js' @@ -152,3 +153,161 @@ describe('getPackageManagerInstallCommand', () => { ).toBe('npm install vitest -D') }) }) + +describe('translateExecuteCommand', () => { + it('should translate npx to bunx for bun', () => { + expect( + formatCommand( + translateExecuteCommand('bun', { + command: 'npx', + args: ['shadcn', 'add', 'button'], + }), + ), + ).toBe('bunx --bun shadcn add button') + }) + it('should translate npx to pnpm dlx for pnpm', () => { + expect( + formatCommand( + translateExecuteCommand('pnpm', { + command: 'npx', + args: ['shadcn', 'add', 'button'], + }), + ), + ).toBe('pnpm dlx shadcn add button') + }) + it('should translate npx to yarn dlx for yarn', () => { + expect( + formatCommand( + translateExecuteCommand('yarn', { + command: 'npx', + args: ['shadcn', 'add', 'button'], + }), + ), + ).toBe('yarn dlx shadcn add button') + }) + it('should translate npx to deno run for deno', () => { + expect( + formatCommand( + translateExecuteCommand('deno', { + command: 'npx', + args: ['shadcn', 'add', 'button'], + }), + ), + ).toBe('deno run npm:shadcn add button') + }) + it('should keep npx -y for npm', () => { + expect( + formatCommand( + translateExecuteCommand('npm', { + command: 'npx', + args: ['shadcn', 'add', 'button'], + }), + ), + ).toBe('npx -y shadcn add button') + }) + it('should pass through non-npx commands unchanged', () => { + expect( + formatCommand( + translateExecuteCommand('bun', { + command: 'node', + args: ['script.js'], + }), + ), + ).toBe('node script.js') + }) + it('should handle missing args gracefully', () => { + expect( + formatCommand(translateExecuteCommand('bun', { command: 'npx' })), + ).toBe('npx') + }) + it('should strip -y flag from npx input', () => { + expect( + formatCommand( + translateExecuteCommand('pnpm', { + command: 'npx', + args: ['-y', 'shadcn', 'add', 'button'], + }), + ), + ).toBe('pnpm dlx shadcn add button') + }) + it('should translate bunx to target package manager', () => { + expect( + formatCommand( + translateExecuteCommand('pnpm', { + command: 'bunx', + args: ['shadcn', 'add', 'button'], + }), + ), + ).toBe('pnpm dlx shadcn add button') + }) + it('should strip --bun flag from bunx input', () => { + expect( + formatCommand( + translateExecuteCommand('pnpm', { + command: 'bunx', + args: ['--bun', 'shadcn', 'add', 'button'], + }), + ), + ).toBe('pnpm dlx shadcn add button') + }) + it('should translate pnpx to target package manager', () => { + expect( + formatCommand( + translateExecuteCommand('yarn', { + command: 'pnpx', + args: ['shadcn', 'add', 'button'], + }), + ), + ).toBe('yarn dlx shadcn add button') + }) + it('should translate pnpm dlx to target package manager', () => { + expect( + formatCommand( + translateExecuteCommand('bun', { + command: 'pnpm', + args: ['dlx', 'shadcn', 'add', 'button'], + }), + ), + ).toBe('bunx --bun shadcn add button') + }) + it('should translate yarn dlx to target package manager', () => { + expect( + formatCommand( + translateExecuteCommand('npm', { + command: 'yarn', + args: ['dlx', 'shadcn', 'add', 'button'], + }), + ), + ).toBe('npx -y shadcn add button') + }) + it('should translate deno run npm: to target package manager', () => { + expect( + formatCommand( + translateExecuteCommand('pnpm', { + command: 'deno', + args: ['run', 'npm:shadcn', 'add', 'button'], + }), + ), + ).toBe('pnpm dlx shadcn add button') + }) + it('should pass through non-execute pnpm commands unchanged', () => { + expect( + formatCommand( + translateExecuteCommand('bun', { + command: 'pnpm', + args: ['install'], + }), + ), + ).toBe('pnpm install') + }) + it('should pass through non-execute deno commands unchanged', () => { + expect( + formatCommand( + translateExecuteCommand('npm', { + command: 'deno', + args: ['task', 'dev'], + }), + ), + ).toBe('deno task dev') + }) +}) diff --git a/packages/create/tests/template-file.test.ts b/packages/create/tests/template-file.test.ts index cc48a2b6..c037bc05 100644 --- a/packages/create/tests/template-file.test.ts +++ b/packages/create/tests/template-file.test.ts @@ -175,4 +175,17 @@ describe('createTemplateFile', () => { expect(output.files['/test/foo-dev.txt']).toEqual('pnpm add foo --dev') expect(output.files['/test/run-dev.txt']).toEqual('pnpm dev') }) + + it('should handle package manager execute script', async () => { + const { environment, output } = createMemoryEnvironment() + const templateFile = createTemplateFile(environment, simpleOptions) + environment.startRun() + await templateFile( + 'exec.txt.ejs', + "<%= getPackageManagerExecuteScript('shadcn', ['add', 'button']) %>", + ) + environment.finishRun() + + expect(output.files['/test/exec.txt']).toEqual('pnpm dlx shadcn add button') + }) })