From ff2135dafb838570601b576ef2fdf0227cf4e9f5 Mon Sep 17 00:00:00 2001 From: Ian Hunter Date: Sun, 1 Feb 2026 19:36:44 -0600 Subject: [PATCH 1/6] bun/pnpm choice --- README.md | 44 +++---- package.json | 112 +++++++++--------- packages/cli/src/cmd/init.ts | 74 +++++++++++- scripts/link-local.js | 8 +- .../repo/.github/workflows/lint-typecheck.yml | 8 +- templates/repo/package.json | 24 ++-- 6 files changed, 173 insertions(+), 97 deletions(-) diff --git a/README.md b/README.md index 8249253e..51bf7f4f 100644 --- a/README.md +++ b/README.md @@ -20,13 +20,13 @@ npx startupkit init Without structure, every project becomes a different architecture. That's **AI slop**. -| Without StartupKit | With StartupKit | -|-------------------|-----------------| -| Where should auth logic live? | `@repo/auth` → Better Auth, ready | -| Prisma or Drizzle? Which pattern? | `@repo/db` → Drizzle + Postgres, configured | -| App router or pages? RSC or client? | Next.js 16 App Router, RSC by default | -| How do I structure shared code? | Monorepo → share everything | -| Which analytics provider? | `@repo/analytics` → Provider-agnostic hooks | +| Without StartupKit | With StartupKit | +| ----------------------------------- | ------------------------------------------- | +| Where should auth logic live? | `@repo/auth` → Better Auth, ready | +| Prisma or Drizzle? Which pattern? | `@repo/db` → Drizzle + Postgres, configured | +| App router or pages? RSC or client? | Next.js 16 App Router, RSC by default | +| How do I structure shared code? | Monorepo → share everything | +| Which analytics provider? | `@repo/analytics` → Provider-agnostic hooks | **Start at 70%.** AI handles the details, not the foundation. @@ -35,7 +35,7 @@ Without structure, every project becomes a different architecture. That's **AI s StartupKit is designed to work seamlessly with AI development tools: - ✅ **Devin** ready -- ✅ **Claude** ready +- ✅ **Claude** ready - ✅ **Amp** ready - ✅ **OpenCode** ready @@ -47,11 +47,13 @@ Every project includes `AGENTS.md` with clear conventions, file placement guidel npx startupkit init cd my-project cp .env.example .env.local -pnpm dev +pnpm dev # or: bun dev ``` Visit [http://localhost:3000](http://localhost:3000) +**Package Manager:** StartupKit supports both [pnpm](https://pnpm.io) and [bun](https://bun.sh). Use whichever you prefer. + ## What's Included ### 📦 Pre-Built Packages @@ -107,32 +109,32 @@ my-project/ ### Development ```bash -pnpm dev # Start all apps -pnpm --filter web dev # Start specific app -pnpm build # Build all packages +pnpm dev # Start all apps (or: bun dev) +pnpm --filter web dev # Start specific app (or: bun --filter web dev) +pnpm build # Build all packages (or: bun run build) ``` ### Database ```bash -pnpm db:generate # Generate migration files -pnpm db:migrate # Apply migrations -pnpm db:studio # Open database GUI +pnpm db:generate # Generate migration files (or: bun db:generate) +pnpm db:migrate # Apply migrations (or: bun db:migrate) +pnpm db:studio # Open database GUI (or: bun db:studio) ``` ### UI Components ```bash -pnpm shadcn add button -pnpm shadcn add dialog +pnpm shadcn add button # or: bun shadcn add button +pnpm shadcn add dialog # or: bun shadcn add dialog ``` ### Code Quality ```bash -pnpm lint # Check all files -pnpm lint:fix # Fix issues -pnpm typecheck # Type check all packages +pnpm lint # Check all files (or: bun lint) +pnpm lint:fix # Fix issues (or: bun lint:fix) +pnpm typecheck # Type check all packages (or: bun typecheck) ``` ## Add New Services @@ -168,7 +170,7 @@ NEXT_PUBLIC_POSTHOG_HOST=https://app.posthog.com - **Database:** PostgreSQL + Drizzle ORM - **Auth:** Better Auth - **Email:** React Email + Resend -- **Monorepo:** pnpm + Turbo +- **Monorepo:** pnpm or bun + Turbo - **Linting:** Biome ## Support & Resources diff --git a/package.json b/package.json index d5ad473e..f8dd0e66 100644 --- a/package.json +++ b/package.json @@ -1,57 +1,57 @@ { - "name": "startupkit", - "private": true, - "author": "Ian Hunter ", - "description": "The 0 to 1000 SaaS Startup Framework", - "license": "ISC", - "type": "module", - "packageManager": "pnpm@9.6.0", - "scripts": { - "clean": "pnpm --stream -r run clean", - "nuke": "find . -name 'node_modules' -type d -exec rm -rf {} +", - "build": "pnpm build:packages && pnpm --stream -r --filter \"./apps/**\" run build", - "build:packages": "pnpm --stream -r --filter \"./packages/**\" run build", - "dev": "turbo run dev --continue --ui stream", - "link:local": "node scripts/link-local.js link", - "unlink:local": "node scripts/link-local.js unlink", - "format": "turbo run format --continue --ui stream", - "format:fix": "turbo run format:fix --continue --ui stream", - "lint": "turbo run lint --continue --ui stream", - "lint:fix": "turbo run lint:fix --continue --ui stream", - "typecheck": "turbo run typecheck --ui stream", - "bump": "./scripts/bump", - "release": "pnpm build:packages && pnpm --filter \"./packages/**\" -r publish --access public --no-git-checks", - "with-env": "dotenv -e .env.local --", - "with-test-env": "dotenv -e .env.test --", - "agents.md": "ruler apply", - "startupkit": "node packages/cli/dist/cli.js" - }, - "pnpm": { - "overrides": { - "react": "19.0.0", - "react-dom": "19.0.0", - "@types/react": "19.0.1", - "@types/react-dom": "19.0.2", - "drizzle-orm": "0.38.4", - "prettier": "3.4.2" - } - }, - "devDependencies": { - "@biomejs/biome": "1.9.4", - "@intellectronica/ruler": "^0.3.11", - "@rollup/plugin-commonjs": "^26.0.1", - "@rollup/plugin-swc": "^0.3.1", - "@swc/core": "^1.7.6", - "dotenv-cli": "7.4.4", - "glob": "^11.0.3", - "pg": "^8.13.1", - "prettier": "3.4.2", - "rollup": "^4.20.0", - "rollup-plugin-esbuild": "^6.1.1", - "rollup-preserve-directives": "^1.1.1", - "tsup": "^8.2.4", - "turbo": "2.5.0", - "typescript": "5.9.2", - "validate-package-exports": "^0.6.0" - } -} \ No newline at end of file + "name": "startupkit", + "private": true, + "author": "Ian Hunter ", + "description": "The 0 to 1000 SaaS Startup Framework", + "license": "ISC", + "type": "module", + "packageManager": "pnpm@10.28.2", + "scripts": { + "clean": "pnpm --stream -r run clean", + "nuke": "find . -name 'node_modules' -type d -exec rm -rf {} +", + "build": "pnpm build:packages && pnpm --stream -r --filter \"./apps/**\" run build", + "build:packages": "pnpm --stream -r --filter \"./packages/**\" run build", + "dev": "turbo run dev --continue --ui stream", + "link:local": "node scripts/link-local.js link", + "unlink:local": "node scripts/link-local.js unlink", + "format": "turbo run format --continue --ui stream", + "format:fix": "turbo run format:fix --continue --ui stream", + "lint": "turbo run lint --continue --ui stream", + "lint:fix": "turbo run lint:fix --continue --ui stream", + "typecheck": "turbo run typecheck --ui stream", + "bump": "./scripts/bump", + "release": "pnpm build:packages && pnpm --filter \"./packages/**\" -r publish --access public --no-git-checks", + "with-env": "dotenv -e .env.local --", + "with-test-env": "dotenv -e .env.test --", + "agents.md": "ruler apply", + "startupkit": "node packages/cli/dist/cli.js" + }, + "pnpm": { + "overrides": { + "react": "19.0.0", + "react-dom": "19.0.0", + "@types/react": "19.0.1", + "@types/react-dom": "19.0.2", + "drizzle-orm": "0.38.4", + "prettier": "3.4.2" + } + }, + "devDependencies": { + "@biomejs/biome": "1.9.4", + "@intellectronica/ruler": "^0.3.11", + "@rollup/plugin-commonjs": "^26.0.1", + "@rollup/plugin-swc": "^0.3.1", + "@swc/core": "^1.7.6", + "dotenv-cli": "7.4.4", + "glob": "^11.0.3", + "pg": "^8.13.1", + "prettier": "3.4.2", + "rollup": "^4.20.0", + "rollup-plugin-esbuild": "^6.1.1", + "rollup-preserve-directives": "^1.1.1", + "tsup": "^8.2.4", + "turbo": "2.5.0", + "typescript": "5.9.2", + "validate-package-exports": "^0.6.0" + } +} diff --git a/packages/cli/src/cmd/init.ts b/packages/cli/src/cmd/init.ts index 4f194465..ac1c1fd9 100644 --- a/packages/cli/src/cmd/init.ts +++ b/packages/cli/src/cmd/init.ts @@ -190,6 +190,24 @@ export async function init(props: { includeStorybook = addStorybook } + // Step 4: Ask about package manager + let packageManager = "pnpm" + if (promptedForName) { + const { pm } = await inquirer.prompt([ + { + type: "list", + name: "pm", + message: "Which package manager would you like to use?", + choices: [ + { name: "pnpm (recommended)", value: "pnpm" }, + { name: "bun", value: "bun" } + ], + default: "pnpm" + } + ]) + packageManager = pm + } + // --- USE DEGit TO CLONE THE REPO STRUCTURE AND PACKAGES --- const repoBase = props.repoArg || "ian/startupkit" const { repoSource, packagesSource, storybookSource } = @@ -232,9 +250,61 @@ export async function init(props: { allowEmptyPaths: true }) + // Replace package manager placeholder in package.json + const rootPackageJsonPath = path.join(destDir, "package.json") + if (existsSync(rootPackageJsonPath)) { + const packageJsonContent = readFileSync(rootPackageJsonPath, "utf8") + const packageManagerVersion = + packageManager === "pnpm" ? "10.28.2" : "1.1.27" + const updatedContent = packageJsonContent + .replace( + /PACKAGE_MANAGER@PACKAGE_MANAGER_VERSION/g, + `${packageManager}@${packageManagerVersion}` + ) + .replace(/PACKAGE_MANAGER/g, packageManager) + writeFileSync(rootPackageJsonPath, updatedContent) + } + + // Replace package manager setup in GitHub workflow + const workflowPath = path.join( + destDir, + ".github/workflows/lint-typecheck.yml" + ) + if (existsSync(workflowPath)) { + const workflowContent = readFileSync(workflowPath, "utf8") + let pmSetupBlock: string + if (packageManager === "pnpm") { + pmSetupBlock = ` - name: Install pnpm + uses: pnpm/action-setup@v4 + + - name: Use Node.js \${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: \${{ matrix.node-version }} + cache: "pnpm"` + } else { + pmSetupBlock = ` - name: Install Bun + uses: oven-sh/setup-bun@v2 + with: + bun-version: latest + + - name: Use Node.js \${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: \${{ matrix.node-version }}` + } + const updatedWorkflow = workflowContent + .replace( + /# PACKAGE_MANAGER_SETUP_START[\s\S]*?# PACKAGE_MANAGER_SETUP_END/, + pmSetupBlock + ) + .replace(/PACKAGE_MANAGER/g, packageManager) + writeFileSync(workflowPath, updatedWorkflow) + } + // Install dependencies await spinner(`Installing dependencies`, async () => { - await exec("pnpm install --no-frozen-lockfile", { cwd: destDir }) + await exec(`${packageManager} install`, { cwd: destDir }) }) // Create or update .env.local with required keys @@ -258,7 +328,7 @@ export async function init(props: { // Generate AI agent instructions await spinner(`Generating AI agent instructions`, async () => { - await exec("pnpm agents.md", { cwd: destDir }) + await exec(`${packageManager} agents.md`, { cwd: destDir }) }) console.log(`\nProject initialized at: ${isCurrentDir ? "." : destDir}`) diff --git a/scripts/link-local.js b/scripts/link-local.js index e7543469..677f555c 100755 --- a/scripts/link-local.js +++ b/scripts/link-local.js @@ -10,19 +10,21 @@ * for any package.json files with names starting with "@startupkit/". * * Usage: - * pnpm link:local - Link template repo packages + * pnpm link:local - Link template repo packages (with pnpm) + * bun link:local - Link template repo packages (with bun) * pnpm unlink:local - Unlink template repo packages * node scripts/link-local.js link * * Examples: * Development: pnpm link:local && cd templates/repo && pnpm install + * Development: bun link:local && cd templates/repo && bun install * Publishing: pnpm unlink:local * CI: node scripts/link-local.js link test-projects/test-startup $GITHUB_WORKSPACE/packages */ -import { glob } from "glob" import { readFileSync, writeFileSync } from "node:fs" import { join, relative, resolve } from "node:path" +import { glob } from "glob" const command = process.argv[2] const targetDir = process.argv[3] || "templates/repo" @@ -143,7 +145,7 @@ async function main() { console.log(`\n🎉 Updated ${changedCount} package.json file(s)`) if (command === "link") { console.log( - '\n⚠️ Run "pnpm install" in templates/repo to update node_modules' + '\n⚠️ Run "pnpm install" or "bun install" in templates/repo to update node_modules' ) } else { console.log("\n⚠️ Remember to update lockfile before committing") diff --git a/templates/repo/.github/workflows/lint-typecheck.yml b/templates/repo/.github/workflows/lint-typecheck.yml index b4e360fc..7d2b9ef1 100644 --- a/templates/repo/.github/workflows/lint-typecheck.yml +++ b/templates/repo/.github/workflows/lint-typecheck.yml @@ -12,6 +12,7 @@ jobs: steps: - uses: actions/checkout@v4 + # PACKAGE_MANAGER_SETUP_START - name: Install pnpm uses: pnpm/action-setup@v4 @@ -20,12 +21,13 @@ jobs: with: node-version: ${{ matrix.node-version }} cache: "pnpm" + # PACKAGE_MANAGER_SETUP_END - name: Install dependencies - run: pnpm install + run: PACKAGE_MANAGER install - name: Run lint - run: pnpm lint + run: PACKAGE_MANAGER lint - name: Run typecheck - run: pnpm typecheck + run: PACKAGE_MANAGER typecheck diff --git a/templates/repo/package.json b/templates/repo/package.json index 2f203b9a..32bcf949 100644 --- a/templates/repo/package.json +++ b/templates/repo/package.json @@ -7,25 +7,25 @@ "engines": { "node": "22" }, - "packageManager": "pnpm@9.15.5", + "packageManager": "PACKAGE_MANAGER@PACKAGE_MANAGER_VERSION", "scripts": { "build": "turbo run build --ui stream", - "clean": "pnpm -r clean", - "dev": "pnpm with-env pnpm -r --parallel --stream run dev", - "test": "pnpm with-test-env turbo run test --ui stream", + "clean": "PACKAGE_MANAGER -r clean", + "dev": "PACKAGE_MANAGER with-env PACKAGE_MANAGER -r --parallel --stream run dev", + "test": "PACKAGE_MANAGER with-test-env turbo run test --ui stream", "format": "turbo run format --continue --ui stream", "format:fix": "turbo run format --continue --ui stream", "lint": "turbo run lint --continue --ui stream", "lint:fix": "turbo run lint:fix --continue --ui stream", "typecheck": "turbo run typecheck --ui stream", - "tunnel": "pnpm with-env sh -c 'ngrok http --url=$TUNNEL_URL 3000'", - "drizzle": "pnpm --filter ./packages/db drizzle", - "db:generate": "pnpm --filter ./packages/db db:generate", - "db:migrate": "pnpm --filter ./packages/db db:migrate", - "db:push": "pnpm --filter ./packages/db db:push", - "db:studio": "pnpm --filter ./packages/db db:studio", + "tunnel": "PACKAGE_MANAGER with-env sh -c 'ngrok http --url=$TUNNEL_URL 3000'", + "drizzle": "PACKAGE_MANAGER --filter ./packages/db drizzle", + "db:generate": "PACKAGE_MANAGER --filter ./packages/db db:generate", + "db:migrate": "PACKAGE_MANAGER --filter ./packages/db db:migrate", + "db:push": "PACKAGE_MANAGER --filter ./packages/db db:push", + "db:studio": "PACKAGE_MANAGER --filter ./packages/db db:studio", "shadcn": "sh ./packages/ui/shadcn.sh", - "storybook": "pnpm --filter ./apps/storybook storybook", + "storybook": "PACKAGE_MANAGER --filter ./apps/storybook storybook", "with-env": "dotenv -e .env.local --", "with-test-env": "dotenv -e .env.test --", "agents.md": "ruler apply" @@ -40,4 +40,4 @@ "typescript": "5.8.3", "startupkit": "^0.6.6" } -} \ No newline at end of file +} From e8d1a69a75dc7ef88d8ab3eb825dcf6e4d941b4b Mon Sep 17 00:00:00 2001 From: Ian Hunter Date: Sun, 1 Feb 2026 21:17:38 -0600 Subject: [PATCH 2/6] try and fix tests --- .github/actions/setup/action.yml | 4 ++-- .github/workflows/build-templates.yml | 8 ++++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/.github/actions/setup/action.yml b/.github/actions/setup/action.yml index b19eb009..469f7f3d 100644 --- a/.github/actions/setup/action.yml +++ b/.github/actions/setup/action.yml @@ -12,6 +12,6 @@ runs: node-version: 22 - name: Setup pnpm - uses: pnpm/action-setup@v2 + uses: pnpm/action-setup@v4 with: - version: 9.6.0 \ No newline at end of file + version: 10.28.2 \ No newline at end of file diff --git a/.github/workflows/build-templates.yml b/.github/workflows/build-templates.yml index a7346339..f5650026 100644 --- a/.github/workflows/build-templates.yml +++ b/.github/workflows/build-templates.yml @@ -19,6 +19,10 @@ jobs: - name: Install dependencies run: pnpm install --frozen-lockfile + - name: Patch template package manager for CI + run: | + node -e "const fs = require('node:fs'); const file = 'templates/repo/package.json'; const content = fs.readFileSync(file, 'utf8'); const updated = content.replace(/PACKAGE_MANAGER@PACKAGE_MANAGER_VERSION/g, 'pnpm@10.28.2').replace(/PACKAGE_MANAGER/g, 'pnpm'); fs.writeFileSync(file, updated);" + - name: Install template dependencies run: pnpm install --frozen-lockfile working-directory: ./templates/repo @@ -67,6 +71,10 @@ jobs: - name: Install dependencies run: pnpm install --frozen-lockfile + - name: Patch template package manager for CI + run: | + node -e "const fs = require('node:fs'); const file = 'templates/repo/package.json'; const content = fs.readFileSync(file, 'utf8'); const updated = content.replace(/PACKAGE_MANAGER@PACKAGE_MANAGER_VERSION/g, 'pnpm@10.28.2').replace(/PACKAGE_MANAGER/g, 'pnpm'); fs.writeFileSync(file, updated);" + - name: Build packages run: pnpm --filter "./packages/**" build From 6b8a6b0fe39a6af91269e7701fe71970d5221948 Mon Sep 17 00:00:00 2001 From: Ian Hunter Date: Sun, 1 Feb 2026 21:46:43 -0600 Subject: [PATCH 3/6] updates --- packages/cli/src/cmd/init.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/cli/src/cmd/init.ts b/packages/cli/src/cmd/init.ts index ac1c1fd9..33f6843a 100644 --- a/packages/cli/src/cmd/init.ts +++ b/packages/cli/src/cmd/init.ts @@ -304,7 +304,11 @@ export async function init(props: { // Install dependencies await spinner(`Installing dependencies`, async () => { - await exec(`${packageManager} install`, { cwd: destDir }) + if (packageManager === "pnpm") { + await exec("pnpm install --no-frozen-lockfile", { cwd: destDir }) + return + } + await exec("bun install", { cwd: destDir }) }) // Create or update .env.local with required keys From b0aca9c4a13a1388e2cbce5f96d5e8c267367e4f Mon Sep 17 00:00:00 2001 From: Ian Hunter Date: Sun, 1 Feb 2026 21:51:26 -0600 Subject: [PATCH 4/6] try bump --- .github/workflows/build-packages.yml | 4 ++-- .github/workflows/npm-publish.yml | 4 ++-- .github/workflows/test-cli.yml | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build-packages.yml b/.github/workflows/build-packages.yml index fde821e6..e5310117 100644 --- a/.github/workflows/build-packages.yml +++ b/.github/workflows/build-packages.yml @@ -26,9 +26,9 @@ jobs: node-version: ${{ matrix.node-version }} - name: Setup pnpm - uses: pnpm/action-setup@v2 + uses: pnpm/action-setup@v4 with: - version: 9.6.0 + version: 10.28.2 - name: Install dependencies run: pnpm install diff --git a/.github/workflows/npm-publish.yml b/.github/workflows/npm-publish.yml index 766ec02b..375f25c2 100644 --- a/.github/workflows/npm-publish.yml +++ b/.github/workflows/npm-publish.yml @@ -29,9 +29,9 @@ jobs: registry-url: 'https://registry.npmjs.org' - name: Setup pnpm - uses: pnpm/action-setup@v2 + uses: pnpm/action-setup@v4 with: - version: 9.6.0 + version: 10.28.2 - name: Install dependencies run: pnpm install diff --git a/.github/workflows/test-cli.yml b/.github/workflows/test-cli.yml index d64c9345..295b25a4 100644 --- a/.github/workflows/test-cli.yml +++ b/.github/workflows/test-cli.yml @@ -18,7 +18,7 @@ jobs: - uses: pnpm/action-setup@v4 with: - version: 9.6.0 + version: 10.28.2 run_install: false - uses: actions/setup-node@v4 @@ -48,7 +48,7 @@ jobs: - uses: pnpm/action-setup@v4 with: - version: 9.6.0 + version: 10.28.2 run_install: false - uses: actions/setup-node@v4 From 5e8b472afd5cc27204402604418edd7760ca5275 Mon Sep 17 00:00:00 2001 From: Ian Hunter Date: Mon, 2 Feb 2026 07:50:26 -0600 Subject: [PATCH 5/6] remove pnpm version --- .github/actions/setup/action.yml | 4 +--- .github/workflows/build-packages.yml | 2 -- .github/workflows/npm-publish.yml | 2 -- .github/workflows/test-cli.yml | 2 -- 4 files changed, 1 insertion(+), 9 deletions(-) diff --git a/.github/actions/setup/action.yml b/.github/actions/setup/action.yml index 469f7f3d..9130c12c 100644 --- a/.github/actions/setup/action.yml +++ b/.github/actions/setup/action.yml @@ -12,6 +12,4 @@ runs: node-version: 22 - name: Setup pnpm - uses: pnpm/action-setup@v4 - with: - version: 10.28.2 \ No newline at end of file + uses: pnpm/action-setup@v4 \ No newline at end of file diff --git a/.github/workflows/build-packages.yml b/.github/workflows/build-packages.yml index e5310117..bbba5b1e 100644 --- a/.github/workflows/build-packages.yml +++ b/.github/workflows/build-packages.yml @@ -27,8 +27,6 @@ jobs: - name: Setup pnpm uses: pnpm/action-setup@v4 - with: - version: 10.28.2 - name: Install dependencies run: pnpm install diff --git a/.github/workflows/npm-publish.yml b/.github/workflows/npm-publish.yml index 375f25c2..24368ddc 100644 --- a/.github/workflows/npm-publish.yml +++ b/.github/workflows/npm-publish.yml @@ -30,8 +30,6 @@ jobs: - name: Setup pnpm uses: pnpm/action-setup@v4 - with: - version: 10.28.2 - name: Install dependencies run: pnpm install diff --git a/.github/workflows/test-cli.yml b/.github/workflows/test-cli.yml index 295b25a4..27731b94 100644 --- a/.github/workflows/test-cli.yml +++ b/.github/workflows/test-cli.yml @@ -18,7 +18,6 @@ jobs: - uses: pnpm/action-setup@v4 with: - version: 10.28.2 run_install: false - uses: actions/setup-node@v4 @@ -48,7 +47,6 @@ jobs: - uses: pnpm/action-setup@v4 with: - version: 10.28.2 run_install: false - uses: actions/setup-node@v4 From 102debc0fe06bf97afdf6aa8c6f3a4c473e89f81 Mon Sep 17 00:00:00 2001 From: Ian Hunter Date: Mon, 2 Feb 2026 10:47:47 -0600 Subject: [PATCH 6/6] option to specify package manager --- .github/workflows/test-cli.yml | 51 ++++++++++++++++++++++++++++++++++ packages/cli/src/cli.ts | 26 +++++++++++++++-- packages/cli/src/cmd/init.ts | 17 +++++++----- 3 files changed, 85 insertions(+), 9 deletions(-) diff --git a/.github/workflows/test-cli.yml b/.github/workflows/test-cli.yml index 27731b94..7a101e7c 100644 --- a/.github/workflows/test-cli.yml +++ b/.github/workflows/test-cli.yml @@ -98,3 +98,54 @@ jobs: if: always() run: rm -rf test-projects + test-cli-bun: + name: CLI Integration - Bun Install + runs-on: ubuntu-latest + needs: test-cli + + steps: + - uses: actions/checkout@v4 + + - uses: pnpm/action-setup@v4 + with: + run_install: false + + - uses: actions/setup-node@v4 + with: + node-version: "22" + cache: "pnpm" + + - name: Install dependencies and build + run: | + pnpm install --frozen-lockfile + pnpm --filter startupkit build + + - name: Setup Bun + uses: oven-sh/setup-bun@v2 + with: + bun-version: latest + + - name: Create and test bun project + run: | + mkdir -p test-projects + cd test-projects + + echo "🔍 Creating project with bun..." + node "$GITHUB_WORKSPACE/packages/cli/dist/cli.js" init --name "test-startup-bun" --repo "ian/startupkit#${{ github.ref_name }}" --package-manager bun + + echo "📂 Verifying packages directory exists..." + test -d test-startup-bun/packages || exit 1 + + echo "🔍 Running typecheck..." + cd test-startup-bun + bun run typecheck + + echo "🏗️ Running build..." + bun run build + + echo "✅ Bun install test passed" + + - name: Clean up + if: always() + run: rm -rf test-projects + diff --git a/packages/cli/src/cli.ts b/packages/cli/src/cli.ts index b0e8ea1b..fb5d418a 100644 --- a/packages/cli/src/cli.ts +++ b/packages/cli/src/cli.ts @@ -6,6 +6,22 @@ import { init } from "./cmd/init" import { initRalphConfig, make } from "./cmd/make" import { upgrade } from "./cmd/upgrade" +interface InitCommandOptions { + name?: string + repo?: string + dir?: string + packageManager?: string +} + +function parsePackageManager( + value: string | undefined +): "pnpm" | "bun" | undefined { + if (!value) return undefined + const normalized = value.trim().toLowerCase() + if (normalized === "pnpm" || normalized === "bun") return normalized + throw new Error(`Unsupported package manager: ${value}. Use "pnpm" or "bun".`) +} + export async function run() { const program = new Command() @@ -17,11 +33,17 @@ export async function run() { .option("--name ", "Name of the app") .option("--repo ", "Template repo to use") .option("--dir ", "Directory to create project in (use . for current)") - .action(async (options) => { + .option( + "--package-manager ", + "Package manager to use (pnpm or bun)" + ) + .action(async (options: InitCommandOptions) => { + const packageManager = parsePackageManager(options.packageManager) await init({ name: options.name, repoArg: options.repo, - dir: options.dir + dir: options.dir, + packageManager }) }) diff --git a/packages/cli/src/cmd/init.ts b/packages/cli/src/cmd/init.ts index 33f6843a..98ad3f84 100644 --- a/packages/cli/src/cmd/init.ts +++ b/packages/cli/src/cmd/init.ts @@ -63,6 +63,13 @@ export interface ResolveDestDirOptions { promptedDirectory?: string } +export interface InitOptions { + name?: string + repoArg?: string + dir?: string + packageManager?: "pnpm" | "bun" +} + export function resolveDestDir(options: ResolveDestDirOptions): string { const { dir, key, cwd, promptedDirectory } = options @@ -103,11 +110,7 @@ export function buildDegitSources(repoBase: string): { } } -export async function init(props: { - name?: string - repoArg?: string - dir?: string -}) { +export async function init(props: InitOptions) { opener() // Step 1: Use provided name or prompt for project name @@ -191,8 +194,8 @@ export async function init(props: { } // Step 4: Ask about package manager - let packageManager = "pnpm" - if (promptedForName) { + let packageManager: "pnpm" | "bun" = props.packageManager ?? "pnpm" + if (!props.packageManager && promptedForName) { const { pm } = await inquirer.prompt([ { type: "list",