GenerateSaaS

Development

Run the dev server, follow the shared code style, and add dependencies the workspace-safe way.

The everyday workflow runs from the repo root through pnpm + Turborepo, which fan each task out to every package and app that defines a matching script. Formatting and type safety are enforced by Prettier, ESLint, and strict TypeScript.

Dev scripts

Run all of these from the repo root.

CommandWhat it does
pnpm devturbo run dev - start all apps (web + api) in watch mode
pnpm buildturbo run build - build every app and package
pnpm startturbo run start - serve the production build
pnpm lintturbo run lint - run ESLint across the workspace
pnpm formatPrettier write over **/*.{ts,tsx,js,vue,json,md,mjs,cjs}
pnpm check-typesturbo run check-types - tsc --noEmit, no output emitted
pnpm testturbo run test - run unit tests

Turbo caches build, lint, and check-types; dev and test are uncached. Run pnpm lint and pnpm check-types before pushing - CI runs the same scripts.

Side processes (run alongside pnpm dev when you need them):

CommandWhat it does
pnpm dev:mailEmail-template preview server on port 3030
pnpm dev:inngestInngest dev server for background jobs (UI on 8288)
pnpm infra / pnpm infra:stopStart/stop local Docker services (only present when you selected Docker-backed infra at init)

Code style

Formatting is owned by Prettier; the project is TypeScript strict - no any, as any, or unsafe casts.

The committed .prettierrc values:

.prettierrc
{
  "useTabs": true,          // indent with tabs
  "tabWidth": 2,            // tab renders as 2 spaces
  "printWidth": 100,        // 100-character line width
  "trailingComma": "none",  // no trailing commas
  "semi": true,             // semicolons required
  "singleQuote": false,     // double quotes
  "arrowParens": "always",  // always parenthesize arrow params
  "endOfLine": "lf"
}

Pre-commit hook

A simple-git-hooks pre-commit hook is installed by the prepare script on pnpm install and ships with your project. It runs the i18n translate step to regenerate non-English locale files for any changed keys, then git adds the regenerated output - packages/i18n/translations, packages/content, packages/translate/.meta, and the generated locale artifacts (i18n-locales.* plus flag-icons.generated.ts) - into the commit. Accept that auto-staged output - only edit the source en/*.json (and the locales record in i18n.ts to add or remove a language); the rest is generated.

The translate step runs only when OPENROUTER_API_KEY is set; without it the hook skips gracefully, so your commits never fail.

Adding dependencies

Install into the workspace that uses a package, never at the root by hand-editing package.json.

# add to an app or a backend package (<app> = your web app's package name)
pnpm --filter <app> add <pkg>
pnpm --filter @repo/api add <pkg>

# dev-only dependency
pnpm --filter @repo/api add -D <pkg>

# remove
pnpm --filter @repo/api remove <pkg>
Run the pnpm --filter <pkg> add command for the target workspace.
Run a plain pnpm install at the root to relink siblings whose peer hashes shifted.

Never hand-edit pnpm-lock.yaml - let pnpm own it. Shared versions are pinned in the catalog: of pnpm-workspace.yaml; reference catalog: from a package's package.json to stay aligned.

Editor setup

The project ships .vscode/launch.json with debug configurations regenerated for your stack. There is no committed .vscode/settings.json - create one and add these values to wire the toolchain into the editor:

  • eslint.workingDirectories set to auto so ESLint resolves the right config per package.
  • files.associations maps *.css to tailwindcss for class IntelliSense.
  • tailwindCSS.experimental.configFile points at tooling/tailwind/globals.css.
  • editor.quickSuggestions.strings is on so class names autocomplete inside string literals.

Install the Prettier, ESLint, and Tailwind CSS IntelliSense extensions, then enable format-on-save in your own editor settings.

Frequently asked questions

Why tabs instead of spaces? useTabs: true lets each developer pick their own display width; tabWidth: 2 only controls how Prettier estimates line length.

Why run pnpm install again after adding a dependency? A filtered add can leave sibling packages with stale peer-hash links; a root pnpm install relinks them so type resolution stays correct.

How do I pin a shared dependency version across packages? Add it to the catalog: block in pnpm-workspace.yaml, then set the dep to "catalog:" in each package's package.json - one source of truth keeps versions aligned.

On this page