diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 9bd8a10..e041ce0 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -91,8 +91,12 @@ jobs: # start prod-app - name: app:run in prod + env: + AUTH_ORIGIN: http://localhost:3000/api/auth run: "cd my-sidebase-app && npm run build && timeout 30 npm run preview || ( [[ $? -eq 124 ]] && echo \"app started and did not exit within first 30 seconds, thats good\" )" # start dev-app and curl from it - name: app:test in prod + env: + AUTH_ORIGIN: http://localhost:3000/api/auth run: "cd my-sidebase-app && timeout 30 npm run dev & (sleep 20 && curl --fail localhost:3000) || ( [[ $? -eq 124 ]] && echo \"app started and did not exit within first 30 seconds, thats good\" )" diff --git a/src/configs/droneCI.ts b/src/configs/droneCI.ts index 7610e32..f13d90d 100644 --- a/src/configs/droneCI.ts +++ b/src/configs/droneCI.ts @@ -10,14 +10,14 @@ trigger: steps: - name: lint-and-typecheck - image: node:20.9.0-slim + image: node:24.10.0-slim commands: - npm i -g @antfu/ni - nci - nr lint - nr typecheck - name: build - image: node:20.9.0-slim + image: node:24.10.0-slim commands: - npm i -g @antfu/ni - nci diff --git a/src/configs/eslint.ts b/src/configs/eslint.ts index 3add995..d0884f8 100644 --- a/src/configs/eslint.ts +++ b/src/configs/eslint.ts @@ -48,17 +48,17 @@ const eslint: Config = { dependencies: [ { name: 'eslint', - version: '^9.18.0', + version: '^9.37.0', isDev: true }, { name: '@antfu/eslint-config', - version: '^3.15.0', + version: '^5.4.1', isDev: true }, { name: 'oxlint', - version: '^0.15.7', + version: '^1.20.0', isDev: true } ], diff --git a/src/configs/github-actions.ts b/src/configs/github-actions.ts index 3086970..f7683bd 100644 --- a/src/configs/github-actions.ts +++ b/src/configs/github-actions.ts @@ -9,15 +9,18 @@ on: pull_request: branches: [ main ] +env: + NODE_VERSION: '24.10.0' + jobs: lint: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - name: Use Node.js 20.6.1 + - name: Use Node.js $/{{ env.NODE_VERSION }} uses: actions/setup-node@v4 with: - node-version: 20.6.1 + node-version: $/{{ env.NODE_VERSION }} - name: Setup run: npm i -g @antfu/ni @@ -32,10 +35,10 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - name: Use Node.js 20.6.1 + - name: Use Node.js $/{{ env.NODE_VERSION }} uses: actions/setup-node@v4 with: - node-version: 20.6.1 + node-version: $/{{ env.NODE_VERSION }} - name: Setup run: npm i -g @antfu/ni diff --git a/src/configs/i18n.ts b/src/configs/i18n.ts index 1c8d4e6..0e475dd 100644 --- a/src/configs/i18n.ts +++ b/src/configs/i18n.ts @@ -50,7 +50,7 @@ const i18n: ModuleConfig = { dependencies: [ { name: '@nuxtjs/i18n', - version: '^9.2.1', + version: '^10.1.0', isDev: true } ], @@ -64,9 +64,7 @@ const i18n: ModuleConfig = { ], strategy: 'prefix_except_default', detectBrowserLanguage: false, - lazy: true, experimental: { - autoImportTranslationFunctions: true, localeDetector: 'localeDetector.ts' } } @@ -81,7 +79,7 @@ const i18n: ModuleConfig = { content: englishLocaleFile }, { - path: 'components/Welcome/I18nDemo.vue', + path: 'app/components/Welcome/I18nDemo.vue', content: i18nDemoComponent } ], diff --git a/src/configs/naiveui.ts b/src/configs/naiveui.ts index 88fbd14..0a88fa6 100644 --- a/src/configs/naiveui.ts +++ b/src/configs/naiveui.ts @@ -42,7 +42,7 @@ const naiveui: ModuleConfig = { dependencies: [ { name: '@bg-dev/nuxt-naiveui', - version: '2.0.0-rc.4', + version: '2.0.0', isDev: true } ], @@ -51,11 +51,11 @@ const naiveui: ModuleConfig = { }, files: [ { - path: 'components/Welcome/NaiveDemo.vue', + path: 'app/components/Welcome/NaiveDemo.vue', content: naiveDemoComponent }, { - path: 'app.vue', + path: 'app/app.vue', content: nuxtAppVueWithNaiveConfig } ], diff --git a/src/configs/prisma.ts b/src/configs/prisma.ts index 30d6993..966d058 100644 --- a/src/configs/prisma.ts +++ b/src/configs/prisma.ts @@ -6,8 +6,12 @@ const prismaRootSchema = `// This is your Prisma schema file, // learn more about it in the docs: https://pris.ly/d/prisma-schema generator client { - provider = "prisma-client-js" - previewFeatures = ["prismaSchemaFolder"] + provider = "prisma-client" + output = "./client" + + // NOTE: \`library\` will be deprecated in Prisma 7 where \`client\`, which is officially stable ("Generally Available") since Prisma 6.16, will become the new default. + // Nevertheless, at this time, there seem to be substantial problems with the \`client\` engine, forcing us to revert to \`library\` + engineType = "library" } datasource db { @@ -21,9 +25,8 @@ datasource db { // This is required for development only. shadowDatabaseUrl = "postgres://postgres@localhost/prisma-shadow?pgbouncer=true&connection_limit=1" } -` -const prismaExampleSchema = `model Example { +model Example { id String @id @default(uuid()) details String } @@ -49,7 +52,8 @@ const prismaExampleEndpoint = `/** export default defineEventHandler(event => event.context.prisma.example.findMany()) ` -const prismaServerMiddleware = `import { PrismaClient } from '@prisma/client' +const prismaServerMiddleware = `import { PrismaPg } from '@prisma/adapter-pg' +import { PrismaClient } from '~~/prisma/client/client' let prisma: PrismaClient @@ -61,7 +65,8 @@ declare module 'h3' { export default eventHandler((event) => { if (!prisma) { - prisma = new PrismaClient() + const adapter = new PrismaPg({ connectionString: process.env.DATABASE_URL }) + prisma = new PrismaClient({ adapter }) } event.context.prisma = prisma }) @@ -166,20 +171,25 @@ const prisma: ModuleConfig = { scripts: [ { name: 'db', - command: 'vite-node prisma/pglite.ts', + command: 'node prisma/pglite.ts', } ], dependencies: [ { name: 'prisma', - version: '^5.22.0', + version: '^6.17.1', isDev: true }, { name: '@prisma/client', - version: '^5.22.0', + version: '^6.17.1', isDev: false }, + { + name: '@prisma/adapter-pg', + version: '^6.17.1', + isDev: false, + }, { name: '@electric-sql/pglite', version: '0.2.13', @@ -189,11 +199,6 @@ const prisma: ModuleConfig = { name: 'pg-gateway', version: '0.3.0-beta.3', isDev: true, - }, - { - name: 'vite-node', - version: '^2.1.5', - isDev: true, } ], nuxtConfig: {}, @@ -203,13 +208,9 @@ const prisma: ModuleConfig = { content: prismaEnvFile }, { - path: 'prisma/schema/schema.prisma', + path: 'prisma/schema.prisma', content: prismaRootSchema }, - { - path: 'prisma/schema/example.prisma', - content: prismaExampleSchema - }, { path: 'server/api/examples.get.ts', content: prismaExampleEndpoint @@ -219,7 +220,7 @@ const prisma: ModuleConfig = { content: prismaServerMiddleware }, { - path: 'components/Welcome/PrismaDemo.vue', + path: 'app/components/Welcome/PrismaDemo.vue', content: prismaDemoComponent, }, { diff --git a/src/configs/sidebase-auth.ts b/src/configs/sidebase-auth.ts index 14b9dc3..e06a799 100644 --- a/src/configs/sidebase-auth.ts +++ b/src/configs/sidebase-auth.ts @@ -86,7 +86,7 @@ const sidebaseAuth: ModuleConfig = { dependencies: [ { name: '@sidebase/nuxt-auth', - version: '^0.9.4', + version: '^1.1.0', isDev: true }, { @@ -105,7 +105,7 @@ const sidebaseAuth: ModuleConfig = { content: nuxtAuthServerFile }, { - path: 'components/Welcome/AuthDemo.vue', + path: 'app/components/Welcome/AuthDemo.vue', content: authDemoComponent } ], diff --git a/src/configs/tailwind.ts b/src/configs/tailwind.ts index fd93b21..df0c4a2 100644 --- a/src/configs/tailwind.ts +++ b/src/configs/tailwind.ts @@ -21,7 +21,7 @@ const tailwind: ModuleConfig = { dependencies: [ { name: '@nuxtjs/tailwindcss', - version: '^6.13.1', + version: '^6.14.0', isDev: true } ], @@ -30,7 +30,7 @@ const tailwind: ModuleConfig = { }, files: [ { - path: 'components/Welcome/TailwindDemo.vue', + path: 'app/components/Welcome/TailwindDemo.vue', content: tailwindDemoComponent, } ], diff --git a/src/configs/trpc.ts b/src/configs/trpc.ts index d4d4dca..6a7c8fb 100644 --- a/src/configs/trpc.ts +++ b/src/configs/trpc.ts @@ -10,7 +10,7 @@ const nuxtTrpcRootConfig = `/** * @see https://trpc.io/docs/v10/router * @see https://trpc.io/docs/v10/procedures */ -import type { Context } from '~/server/trpc/context' +import type { Context } from '~~/server/trpc/context' import { initTRPC } from '@trpc/server' import superjson from 'superjson' @@ -69,8 +69,8 @@ export type Context = inferAsyncReturnType ` const nuxtTrpcApiHandler = `import { createNuxtApiHandler } from 'trpc-nuxt' -import { createContext } from '~/server/trpc/context' -import { appRouter } from '~/server/trpc/routers' +import { createContext } from '~~/server/trpc/context' +import { appRouter } from '~~/server/trpc/routers' // export API handler export default createNuxtApiHandler({ @@ -79,7 +79,7 @@ export default createNuxtApiHandler({ }) ` -const nuxtTrpcPlugin = `import type { AppRouter } from '~/server/trpc/routers' +const nuxtTrpcPlugin = `import type { AppRouter } from '~~/server/trpc/routers' import superjson from 'superjson' import { createTRPCNuxtClient, httpBatchLink } from 'trpc-nuxt/client' @@ -89,10 +89,10 @@ export default defineNuxtPlugin(() => { * built on top of \`useAsyncData\`. */ const client = createTRPCNuxtClient({ - transformer: superjson, links: [ httpBatchLink({ - url: '/api/trpc' + url: '/api/trpc', + transformer: superjson }) ] }) @@ -126,33 +126,33 @@ const hello = await $client.hello.useQuery({ text: 'client' }) ` const trpc: ModuleConfig = { - humanReadableName: 'tRPC 10', + humanReadableName: 'tRPC', description: 'Build end-to-end typesafe APIs in Nuxt applications. See more: https://trpc.io/', scripts: [], dependencies: [ { name: '@trpc/server', - version: '^10.45.2', + version: '^11.6.0', isDev: false }, { name: '@trpc/client', - version: '^10.45.2', + version: '^11.6.0', isDev: false }, { name: 'trpc-nuxt', - version: '^0.10.21', + version: '^1.2.0', isDev: false }, { name: 'zod', - version: '^3.23.8', + version: '^4.1.12', isDev: false }, { name: 'superjson', - version: '^2.2.1', + version: '^2.2.2', isDev: false } ], @@ -179,11 +179,11 @@ const trpc: ModuleConfig = { content: nuxtTrpcApiHandler }, { - path: 'plugins/trpcClient.ts', + path: 'app/plugins/trpcClient.ts', content: nuxtTrpcPlugin }, { - path: 'components/Welcome/TRPCDemo.vue', + path: 'app/components/Welcome/TRPCDemo.vue', content: trpcDemoComponent, } ], diff --git a/src/configs/typescript.ts b/src/configs/typescript.ts index d6e93e2..671d260 100644 --- a/src/configs/typescript.ts +++ b/src/configs/typescript.ts @@ -11,12 +11,12 @@ const scripts: Config = { dependencies: [ { name: 'vue-tsc', - version: '2.2.8', + version: '^3.1.1', isDev: true }, { name: 'typescript', - version: '5.7.3', + version: '^5.9.3', isDev: true, } ], diff --git a/src/index.ts b/src/index.ts index 164e88f..2be6345 100644 --- a/src/index.ts +++ b/src/index.ts @@ -21,7 +21,6 @@ async function main() { // Collect User preferences let preferences: Preferences = { setProjectName: 'my-sidebase-app', - setStack: 'merino', addModules: ['prisma', 'sidebase-auth', 'trpc', 'tailwind', 'naiveui', 'i18n'], runGitInit: true, addCi: 'github', @@ -37,7 +36,7 @@ async function main() { } // 1. Download the Nuxt 3 template - const template = await wrapInSpinner(`Adding Nuxt 3 ${preferences.setStack}-template`, downloadTemplate, preferences) + const template = await wrapInSpinner(`Adding Nuxt`, downloadTemplate, preferences) // 2. Get Configs and modules const { configs, modules } = getConfigs(preferences) diff --git a/src/messages/goodbye.ts b/src/messages/goodbye.ts index 42ca3ce..5e69330 100644 --- a/src/messages/goodbye.ts +++ b/src/messages/goodbye.ts @@ -29,7 +29,7 @@ export function sayGoodbye(preferences: Preferences) { sayCommand(`${packageManager} install`, 'Install project dependencies') } - if (preferences.addModules?.includes('prisma') || preferences.setStack === 'cheviot') { + if (preferences.addModules?.includes('prisma')) { sayCommand(`${packageManager} run db`, 'Start the local postgres database in a new window') sayCommand('npx prisma db push', 'Initialize the database & Prisma client') } diff --git a/src/prompts.ts b/src/prompts.ts index 571a260..c8c1d18 100644 --- a/src/prompts.ts +++ b/src/prompts.ts @@ -1,17 +1,11 @@ import prompts from 'prompts' -import type { PromptObject, PromptType } from 'prompts' +import type { PromptObject } from 'prompts' import { say } from './messages' import { getUserPkgManager } from './utils/getUserPkgManager' import { getRandomProjectNoun } from './utils/getRandomProjectNoun' -import type { Preferences, Stack } from './types' +import type { Preferences } from './types' import { modules } from './configs' -function skipIf(stacksToSkip: Stack[], promptType: PromptType) { - return (_: unknown, preferences: Record) => { - return stacksToSkip.includes(preferences.setStack as Stack) ? null : promptType - } -} - const PROMPT_QUESTIONS: PromptObject[] = [ { type: 'text', @@ -20,17 +14,7 @@ const PROMPT_QUESTIONS: PromptObject[] = [ initial: `my-sidebase-${getRandomProjectNoun()}` }, { - type: 'select', - name: 'setStack', - message: 'What stack would you like to use for your new project? More information: https://sidebase.io/sidebase/welcome/stacks', - choices: [ - { title: 'Merino', description: 'A modular stack that let\'s you choose configuration and modules, e.g.: Want Prisma ORM or not? Want Authentication or not? ... Merino is ideal if you want fine-grained control', value: 'merino' }, - { title: 'Cheviot', description: 'A batteries-included stack where most decisions were made for you. Cheviot is ideal if you want to just get going with an opinionated stack that works', value: 'cheviot' }, - ], - initial: 0 - }, - { - type: skipIf(['cheviot'], 'multiselect'), + type: 'multiselect', name: 'addModules', message: 'Which modules would you like to use?', choices: Object.entries(modules).map(( @@ -43,7 +27,7 @@ const PROMPT_QUESTIONS: PromptObject[] = [ initial: true, }, { - type: skipIf(['cheviot'], 'select'), + type: 'select', name: 'addCi', message: 'Initialize a default CI pipeline?', choices: [ diff --git a/src/steps/1.downloadTemplate.ts b/src/steps/1.downloadTemplate.ts index 7b637b8..d625dae 100644 --- a/src/steps/1.downloadTemplate.ts +++ b/src/steps/1.downloadTemplate.ts @@ -2,18 +2,13 @@ import { downloadTemplate } from 'giget' import { say } from '../messages' import type { Preferences } from '../types' -const KNOWN_TEMPLATES = { - merino: 'github:sidebase/templates#nuxt-3.15.4', - cheviot: 'community/sidebase' -} +const TEMPLATE_NAME = 'github:sidebase/templates#nuxt-4.1.2' export default async (preferences: Preferences) => { - const templateName = KNOWN_TEMPLATES[preferences.setStack as keyof typeof KNOWN_TEMPLATES] - // 1. Download template let template try { - template = await downloadTemplate(templateName, { + template = await downloadTemplate(TEMPLATE_NAME, { dir: preferences.setProjectName, registry: 'https://raw.githubusercontent.com/nuxt/starter/templates/templates' }) diff --git a/src/steps/2.getConfigs.ts b/src/steps/2.getConfigs.ts index 1d1a65c..fb79072 100644 --- a/src/steps/2.getConfigs.ts +++ b/src/steps/2.getConfigs.ts @@ -5,12 +5,7 @@ import { getUserPkgManager } from '../utils/getUserPkgManager' export default function (preferences: Preferences) { const setConfigs: Config[] = [] - // 1. Check if stack is not cheviot - if (preferences.setStack === 'cheviot') { - return { configs: [], modules: [] } - } - - // 2. Add Prebuilt CI pipeline + // 1. Add Prebuilt CI pipeline if (preferences.addCi === 'github') { setConfigs.push(configs['github-actions']) } @@ -18,17 +13,17 @@ export default function (preferences: Preferences) { setConfigs.push(configs.droneCI) } - // 3. Add required base configs + // 2. Add required base configs setConfigs.push(configs.eslint) setConfigs.push(configs.typescript) setConfigs.push(configs.vscode) - // 4. If pnpm is used, add `.npmrc` + // 3. If pnpm is used, add `.npmrc` if (getUserPkgManager() === 'pnpm') { setConfigs.push(configs.pnpm) } - // 5. Get Modules + // 4. Get Modules const setModules = preferences.addModules?.map(key => modules[key]) ?? [] return { configs: setConfigs, modules: setModules } diff --git a/src/steps/4.buildNuxtConfig.ts b/src/steps/4.buildNuxtConfig.ts index 9796fe8..fa13edb 100644 --- a/src/steps/4.buildNuxtConfig.ts +++ b/src/steps/4.buildNuxtConfig.ts @@ -20,11 +20,8 @@ export default async function (templateDir: string, configs: Config[], modules: // 2. Build base Nuxt Config let nuxtConfig = { - compatibilityDate: '2024-08-19', + compatibilityDate: '2025-07-15', devtools: { enabled: true }, - typescript: { - shim: false, - }, } as NuxtConfig for (const nuxtConfigExtension of nuxtConfigExtensions) { diff --git a/src/steps/5.writeFiles.ts b/src/steps/5.writeFiles.ts index 99d3b3b..12ce341 100644 --- a/src/steps/5.writeFiles.ts +++ b/src/steps/5.writeFiles.ts @@ -20,7 +20,7 @@ export default async function (templateDir: string, configs: Config[], modules: ` - await writeFile(resolver('app.vue'), nuxtAppVue) + await writeFile(resolver('app/app.vue'), nuxtAppVue) // 1. Collect all Files const filesToAdd: File[] = [] @@ -36,12 +36,12 @@ export default async function (templateDir: string, configs: Config[], modules: // 3. Write index.vue with a nice welcome message as well as links to sub-pages const nuxtPagesIndexVue = generateIndexVue(modules) - await mkdir(resolver('pages'), { recursive: true }) - await writeFile(resolver('pages/index.vue'), nuxtPagesIndexVue) + await mkdir(resolver('app/pages'), { recursive: true }) + await writeFile(resolver('app/pages/index.vue'), nuxtPagesIndexVue) // 4. Write ButtonLink.vue for the module components if (modules.length > 0) { - await mkdir(resolver('components/Welcome'), { recursive: true }) - await writeFile(resolver('components/Welcome/ButtonLink.vue'), buttonLink) + await mkdir(resolver('app/components/Welcome'), { recursive: true }) + await writeFile(resolver('app/components/Welcome/ButtonLink.vue'), buttonLink) } } diff --git a/src/steps/8.addReadMe.ts b/src/steps/8.addReadMe.ts index ee7d559..e1b2fa5 100644 --- a/src/steps/8.addReadMe.ts +++ b/src/steps/8.addReadMe.ts @@ -5,40 +5,21 @@ import { getUserPkgManager } from '../utils/getUserPkgManager' import { modules } from '../configs' function makeReadme(preferences: Preferences) { - const { setProjectName = 'sidebase', setStack = undefined, addModules = [], addCi = 'none' } = preferences + const { setProjectName = 'sidebase', addModules = [], addCi = 'none' } = preferences let selectedFeatures = [] - if (setStack === 'merino') { - selectedFeatures = addModules.map((module: keyof typeof modules) => `- ${modules[module].humanReadableName}`) - if (addCi === 'github') { - selectedFeatures.push('- GitHub Actions based CI') - } - selectedFeatures.push('- Linting via ESLint and @antfu/eslint-config') - } - else { - selectedFeatures = [ - '- Database models, migrations, queries and easy DB-switching via Prisma', - '- Deep Prisma integration: Use the client in your endpoints via nuxt-prisma, Prisma client is auto-generated for npm run dev and other commands and more', - '- Frontend- and Backend data-transformation via nuxt-parse and zod', - '- In-memory development SQL-database via sqlite3', - '- Linting via eslint', - '- Test management, Test UI, component snapshotting via vitest', - '- Component tests via test-library/vue', - '- Nuxt 3 native API testing via @nuxt/test-utils', - '- Code coverage via c8', - '- CSS utilities via TailwindCSS', - '- CSS components via Naive UI', - '- Type checking in script and template via Volar / vue-tsc', - '- Code editor configuration via .editorconfig files and a portable .settings/ folder whith best-practice VS Code settings and extensions for Vue 3 / Nuxt 3 development', - ] + selectedFeatures = addModules.map((module: keyof typeof modules) => `- ${modules[module].humanReadableName}`) + if (addCi === 'github') { + selectedFeatures.push('- GitHub Actions based CI') } + selectedFeatures.push('- Linting via ESLint and @antfu/eslint-config') const tasksPostInstall = addModules.map(module => modules[module].tasksPostInstall).flat() const packageManager = getUserPkgManager() return `# ${setProjectName}-app -This is a [sidebase ${setStack}](https://sidebase.io/) app created by running \`${packageManager} create sidebase@latest\`. This project uses the following technologies for a great developer- and user-experience: +This is a [sidebase](https://sidebase.io/) app created by running \`${packageManager} create sidebase@latest\`. This project uses the following technologies for a great developer- and user-experience: - [TypeScript](https://www.typescriptlang.org/) - [Nuxt 3](https://nuxt.com) ${selectedFeatures.join('\n')} diff --git a/src/types.ts b/src/types.ts index b50b9be..5234b63 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,10 +1,8 @@ import type { NuxtConfig } from '@nuxt/schema' import type { Modules } from './configs' -export type Stack = 'merino' | 'cheviot' export interface Preferences { setProjectName: string - setStack: Stack addModules?: Modules[] runGitInit: boolean addCi?: 'github' | 'drone' diff --git a/src/utils/logTelemetry.ts b/src/utils/logTelemetry.ts index 407f683..beba392 100644 --- a/src/utils/logTelemetry.ts +++ b/src/utils/logTelemetry.ts @@ -16,7 +16,7 @@ export async function logTelemetry(preferences: Preferences) { } // We do not want to know identifiable information, only pass: Stack + selected modules - const annonymizedPreferences = new URLSearchParams({ ref: preferences.setStack, utm_content: (preferences.addModules || []).join(',') || 'null' }) + const annonymizedPreferences = new URLSearchParams({ ref: 'merino', utm_content: (preferences.addModules || []).join(',') || 'null' }) const headers = { 'User-Agent': 'Mozilla/5.0', 'X-Forwarded-For': `2.175.${Math.floor(Math.random() * 255)}.${Math.floor(Math.random() * 255)}` } return fetch('https://plausible.io/api/event', { method: 'POST',