virastack 1.0.6 → 1.1.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (132) hide show
  1. package/README.md +29 -17
  2. package/package.json +5 -4
  3. package/src/commands/add.js +12 -5
  4. package/src/commands/init.js +68 -32
  5. package/src/i18n.js +36 -20
  6. package/src/index.js +15 -1
  7. package/src/utils/agents.js +85 -0
  8. package/src/utils/args.js +1 -1
  9. package/src/utils/git.js +18 -4
  10. package/src/utils/next-agents-md.js +40 -0
  11. package/src/utils/package-manager.js +5 -2
  12. package/src/utils/tools.js +1 -1
  13. package/templates/nextjs/.github/workflows/ci.yml +3 -0
  14. package/templates/nextjs/.prettierignore +9 -0
  15. package/templates/nextjs/.prettierrc +0 -1
  16. package/templates/nextjs/README.md +21 -15
  17. package/templates/nextjs/eslint.config.mjs +41 -0
  18. package/templates/nextjs/knip.config.js +2 -0
  19. package/templates/nextjs/package.json +14 -3
  20. package/templates/nextjs/pnpm-workspace.yaml +1 -0
  21. package/templates/nextjs/public/llms.txt +13 -11
  22. package/templates/nextjs/src/config/site.config.ts +1 -1
  23. package/templates/nextjs/src/features/landing/api/get-users.api.test.ts +34 -0
  24. package/templates/nextjs/src/features/landing/api/index.ts +1 -0
  25. package/templates/nextjs/src/features/landing/api/user.queries.ts +10 -0
  26. package/templates/nextjs/src/features/landing/components/CartDemo.test.tsx +22 -0
  27. package/templates/nextjs/src/features/landing/components/Features.tsx +1 -1
  28. package/templates/nextjs/src/features/landing/components/Hero.tsx +2 -3
  29. package/templates/nextjs/src/features/landing/components/Reveal.tsx +1 -1
  30. package/templates/nextjs/src/features/landing/constants/index.ts +1 -0
  31. package/templates/nextjs/src/features/landing/helpers/get-initials.test.ts +17 -0
  32. package/templates/nextjs/src/features/landing/hooks/use-users.ts +2 -5
  33. package/templates/nextjs/src/features/landing/icons/tanstack.tsx +2 -314
  34. package/templates/nextjs/src/test/render.tsx +19 -0
  35. package/templates/nextjs/src/test/server.ts +7 -0
  36. package/templates/nextjs/src/test/setup.ts +15 -0
  37. package/templates/nextjs/tsconfig.json +0 -2
  38. package/templates/nextjs/vitest.config.ts +13 -0
  39. package/templates/tanstack/.github/workflows/ci.yml +3 -0
  40. package/templates/tanstack/.prettierignore +9 -0
  41. package/templates/tanstack/.prettierrc +0 -1
  42. package/templates/tanstack/README.md +21 -15
  43. package/templates/tanstack/eslint.config.js +47 -0
  44. package/templates/tanstack/package.json +12 -3
  45. package/templates/tanstack/pnpm-workspace.yaml +1 -0
  46. package/templates/tanstack/public/llms.txt +12 -10
  47. package/templates/tanstack/src/config/site.config.ts +1 -1
  48. package/templates/tanstack/src/features/landing/api/get-users.api.test.ts +34 -0
  49. package/templates/tanstack/src/features/landing/api/index.ts +1 -0
  50. package/templates/tanstack/src/features/landing/api/user.queries.ts +10 -0
  51. package/templates/tanstack/src/features/landing/components/CartDemo.test.tsx +22 -0
  52. package/templates/tanstack/src/features/landing/components/Features.tsx +1 -1
  53. package/templates/tanstack/src/features/landing/components/Hero.tsx +2 -3
  54. package/templates/tanstack/src/features/landing/components/Reveal.tsx +1 -1
  55. package/templates/tanstack/src/features/landing/components/UsersDemo.tsx +1 -1
  56. package/templates/tanstack/src/features/landing/constants/index.ts +1 -0
  57. package/templates/tanstack/src/features/landing/helpers/get-initials.test.ts +17 -0
  58. package/templates/tanstack/src/features/landing/hooks/use-users.ts +3 -6
  59. package/templates/tanstack/src/features/landing/icons/tanstack.tsx +2 -314
  60. package/templates/tanstack/src/test/render.tsx +19 -0
  61. package/templates/tanstack/src/test/server.ts +7 -0
  62. package/templates/tanstack/src/test/setup.ts +15 -0
  63. package/templates/tanstack/vitest.config.ts +13 -0
  64. package/templates-i18n/nextjs/.github/workflows/ci.yml +3 -0
  65. package/templates-i18n/nextjs/.prettierignore +9 -0
  66. package/templates-i18n/nextjs/.prettierrc +0 -1
  67. package/templates-i18n/nextjs/README.md +21 -15
  68. package/templates-i18n/nextjs/eslint.config.mjs +41 -0
  69. package/templates-i18n/nextjs/knip.config.js +4 -2
  70. package/templates-i18n/nextjs/{src/messages → messages}/en.json +1 -1
  71. package/templates-i18n/nextjs/{src/messages → messages}/tr.json +1 -1
  72. package/templates-i18n/nextjs/package.json +14 -3
  73. package/templates-i18n/nextjs/pnpm-workspace.yaml +1 -0
  74. package/templates-i18n/nextjs/public/llms.txt +11 -10
  75. package/templates-i18n/nextjs/src/components/shared/LanguageSwitcher.tsx +2 -2
  76. package/templates-i18n/nextjs/src/config/site.config.ts +1 -1
  77. package/templates-i18n/nextjs/src/features/landing/api/get-users.api.test.ts +34 -0
  78. package/templates-i18n/nextjs/src/features/landing/api/index.ts +1 -0
  79. package/templates-i18n/nextjs/src/features/landing/api/user.queries.ts +10 -0
  80. package/templates-i18n/nextjs/src/features/landing/components/CartDemo.test.tsx +22 -0
  81. package/templates-i18n/nextjs/src/features/landing/components/Hero.tsx +1 -2
  82. package/templates-i18n/nextjs/src/features/landing/components/Reveal.tsx +1 -1
  83. package/templates-i18n/nextjs/src/features/landing/constants/index.ts +1 -0
  84. package/templates-i18n/nextjs/src/features/landing/helpers/get-initials.test.ts +17 -0
  85. package/templates-i18n/nextjs/src/features/landing/hooks/use-users.ts +2 -5
  86. package/templates-i18n/nextjs/src/features/landing/icons/tanstack.tsx +2 -314
  87. package/templates-i18n/nextjs/src/i18n/request.ts +1 -1
  88. package/templates-i18n/nextjs/src/test/render.tsx +26 -0
  89. package/templates-i18n/nextjs/src/test/server.ts +7 -0
  90. package/templates-i18n/nextjs/src/test/setup.ts +15 -0
  91. package/templates-i18n/nextjs/tsconfig.json +1 -2
  92. package/templates-i18n/nextjs/vitest.config.ts +13 -0
  93. package/templates-i18n/tanstack/.github/workflows/ci.yml +3 -0
  94. package/templates-i18n/tanstack/.prettierignore +12 -0
  95. package/templates-i18n/tanstack/.prettierrc +0 -1
  96. package/templates-i18n/tanstack/README.md +21 -15
  97. package/templates-i18n/tanstack/_gitignore +3 -0
  98. package/templates-i18n/tanstack/eslint.config.js +47 -0
  99. package/templates-i18n/tanstack/knip.config.js +2 -2
  100. package/templates-i18n/tanstack/messages/en.json +1 -1
  101. package/templates-i18n/tanstack/messages/tr.json +1 -1
  102. package/templates-i18n/tanstack/package.json +14 -4
  103. package/templates-i18n/tanstack/pnpm-workspace.yaml +1 -0
  104. package/templates-i18n/tanstack/public/llms.txt +7 -6
  105. package/templates-i18n/tanstack/src/components/shared/LanguageSwitcher.tsx +2 -2
  106. package/templates-i18n/tanstack/src/config/site.config.ts +1 -1
  107. package/templates-i18n/tanstack/src/features/landing/api/get-users.api.test.ts +34 -0
  108. package/templates-i18n/tanstack/src/features/landing/api/index.ts +1 -0
  109. package/templates-i18n/tanstack/src/features/landing/api/user.queries.ts +10 -0
  110. package/templates-i18n/tanstack/src/features/landing/components/CartDemo.test.tsx +22 -0
  111. package/templates-i18n/tanstack/src/features/landing/components/Hero.tsx +1 -2
  112. package/templates-i18n/tanstack/src/features/landing/components/Reveal.tsx +1 -1
  113. package/templates-i18n/tanstack/src/features/landing/constants/index.ts +1 -0
  114. package/templates-i18n/tanstack/src/features/landing/helpers/get-initials.test.ts +17 -0
  115. package/templates-i18n/tanstack/src/features/landing/hooks/use-users.ts +3 -6
  116. package/templates-i18n/tanstack/src/features/landing/icons/tanstack.tsx +2 -314
  117. package/templates-i18n/tanstack/src/test/render.tsx +19 -0
  118. package/templates-i18n/tanstack/src/test/server.ts +7 -0
  119. package/templates-i18n/tanstack/src/test/setup.ts +15 -0
  120. package/templates-i18n/tanstack/vitest.config.ts +13 -0
  121. package/templates/nextjs/.eslintcache +0 -1
  122. package/templates/nextjs/src/features/landing/data/index.ts +0 -1
  123. package/templates/tanstack/.eslintcache +0 -1
  124. package/templates/tanstack/src/features/landing/data/index.ts +0 -1
  125. package/templates-i18n/nextjs/.eslintcache +0 -1
  126. package/templates-i18n/nextjs/src/features/landing/data/index.ts +0 -1
  127. package/templates-i18n/tanstack/.eslintcache +0 -1
  128. package/templates-i18n/tanstack/src/features/landing/data/index.ts +0 -1
  129. /package/templates/nextjs/src/features/landing/{data/features.data.tsx → constants/features.constants.tsx} +0 -0
  130. /package/templates/tanstack/src/features/landing/{data/features.data.tsx → constants/features.constants.tsx} +0 -0
  131. /package/templates-i18n/nextjs/src/features/landing/{data/features.data.tsx → constants/features.constants.tsx} +0 -0
  132. /package/templates-i18n/tanstack/src/features/landing/{data/features.data.tsx → constants/features.constants.tsx} +0 -0
package/src/utils/git.js CHANGED
@@ -10,9 +10,10 @@ export async function isInsideGitRepo(dir) {
10
10
  }
11
11
 
12
12
  /**
13
- * Best-effort git init + initial commit. Never throws: a missing git binary
14
- * or an already-initialized repo (e.g. scaffolding into a monorepo) should
15
- * not fail the whole scaffold. Returns whether a new repo was created.
13
+ * Best-effort `git init`. Runs before the dependency install so the
14
+ * template's `prepare` script (husky) can install its hooks. Never throws:
15
+ * a missing git binary or an existing repo (e.g. a monorepo) should not fail
16
+ * the scaffold. Returns whether a new repo was created.
16
17
  */
17
18
  export async function initGitRepo(dir) {
18
19
  if (!(await commandExists("git"))) return false;
@@ -20,8 +21,21 @@ export async function initGitRepo(dir) {
20
21
 
21
22
  try {
22
23
  await runCommand("git", ["init"], { cwd: dir });
24
+ return true;
25
+ } catch {
26
+ return false;
27
+ }
28
+ }
29
+
30
+ /**
31
+ * Commits every file as the initial commit, after all setup steps ran so the
32
+ * working tree is clean. Hooks are skipped: they lint the user's own changes,
33
+ * not the generated scaffold. Never throws; returns whether it committed.
34
+ */
35
+ export async function commitInitial(dir) {
36
+ try {
23
37
  await runCommand("git", ["add", "-A"], { cwd: dir });
24
- await runCommand("git", ["commit", "-m", "chore: initial commit from virastack"], {
38
+ await runCommand("git", ["commit", "--no-verify", "-m", "chore: initial commit from virastack"], {
25
39
  cwd: dir,
26
40
  env: {
27
41
  ...process.env,
@@ -0,0 +1,40 @@
1
+ import { promises as fs } from "node:fs";
2
+ import path from "node:path";
3
+
4
+ const BEGIN = "<!-- BEGIN:nextjs-agent-rules -->";
5
+
6
+ /**
7
+ * The block Next.js 16.2+ documents for AGENTS.md
8
+ * (node_modules/next/dist/docs/01-app/02-guides/ai-agents.md): it points
9
+ * agents at the docs bundled with the installed `next` version. Written
10
+ * directly instead of running `@next/codemod agents-md`, which only targets
11
+ * Next.js 16.1 and earlier.
12
+ */
13
+ export const NEXT_AGENTS_BLOCK = `${BEGIN}
14
+
15
+ # Next.js: ALWAYS read docs before coding
16
+
17
+ Before any Next.js work, find and read the relevant doc in \`node_modules/next/dist/docs/\`. Your training data is outdated — the docs are the source of truth.
18
+
19
+ <!-- END:nextjs-agent-rules -->
20
+ `;
21
+
22
+ /**
23
+ * Adds the Next.js block to AGENTS.md. An existing file keeps its content and
24
+ * gets the block appended; a file that already has the block is left alone.
25
+ * Returns whether the file changed.
26
+ */
27
+ export async function writeNextAgentsBlock(targetDir) {
28
+ const file = path.join(targetDir, "AGENTS.md");
29
+ let current = null;
30
+ try {
31
+ current = await fs.readFile(file, "utf8");
32
+ } catch {
33
+ // No AGENTS.md yet.
34
+ }
35
+
36
+ if (current?.includes(BEGIN)) return false;
37
+ const content = current ? `${current.replace(/\s*$/, "")}\n\n${NEXT_AGENTS_BLOCK}` : NEXT_AGENTS_BLOCK;
38
+ await fs.writeFile(file, content);
39
+ return true;
40
+ }
@@ -1,3 +1,5 @@
1
+ import { formatAgents, getAiPackageSpec } from "./agents.js";
2
+
1
3
  /**
2
4
  * Detects the package manager the CLI was invoked with, mirroring the
3
5
  * technique used by create-next-app and create-vite (npm_config_user_agent).
@@ -28,6 +30,7 @@ export function getRunDevCommand(pm) {
28
30
  return pm === "npm" ? "npm run dev" : `${pm} dev`;
29
31
  }
30
32
 
31
- export function getAiInitCommand() {
32
- return "npx --yes @virastack/ai init --force";
33
+ /** Manual ViraStack AI command for the selected agents (never `--force`). */
34
+ export function getAiInitCommand(agents) {
35
+ return `npx ${getAiPackageSpec()} init --agent ${formatAgents(agents)}`;
33
36
  }
@@ -4,7 +4,7 @@
4
4
  * - "dependency" tools are added straight to package.json and installed
5
5
  * like any other npm package (e.g. @virastack/mask).
6
6
  * - "postinstall-cli" tools ship their own `init` command that writes files
7
- * into the target project (e.g. @virastack/ai writes .cursor/rules), so we
7
+ * into the target project (e.g. @virastack/ai writes agent rules), so we
8
8
  * shell out to them instead of treating them as a runtime dependency.
9
9
  */
10
10
  export const TOOLS = [
@@ -37,5 +37,8 @@ jobs:
37
37
  - name: Knip
38
38
  run: pnpm run knip
39
39
 
40
+ - name: Test
41
+ run: pnpm run test
42
+
40
43
  - name: Build
41
44
  run: pnpm run build
@@ -4,3 +4,12 @@ pnpm-lock.yaml
4
4
  public
5
5
  CHANGELOG.md
6
6
  .changeset
7
+
8
+ # Managed by ViraStack AI and the skills CLI
9
+ AGENTS.md
10
+ CLAUDE.md
11
+ .agents
12
+ .claude
13
+ .cursor
14
+ .virastack
15
+ skills-lock.json
@@ -28,7 +28,6 @@
28
28
  "^@/providers($|/)",
29
29
  "^@/stores($|/)",
30
30
  "^@/hooks($|/)",
31
- "^@/data($|/)",
32
31
  "",
33
32
  "^@/components($|/)",
34
33
  "^@/layout($|/)",
@@ -33,10 +33,10 @@ Production-grade Next.js starter with feature-sliced architecture, strict TypeSc
33
33
  ## Why ViraStack
34
34
 
35
35
  - **Opinionated, not opaque** — clear folder rules and placement conventions; nothing important is hidden behind magic
36
- - **Agent-ready by default** — `AGENTS.md`, Cursor rules, `llms.txt`, and design skills ship with every project
36
+ - **Agent-ready by default** — `AGENTS.md`, rules for Claude Code and Cursor, `llms.txt`, and agent skills ship with every project
37
37
  - **Own your UI** — Base UI primitives in `components/ui` (shadcn-style), fully editable
38
38
  - **State triad** — TanStack Query (server), Zustand (client), nuqs (URL) — each with a dedicated job
39
- - **DX that sticks** — ESLint, Prettier, Knip, Husky, Commitlint, and GitHub Actions CI out of the box
39
+ - **DX that sticks** — ESLint, Prettier, Knip, Vitest, Husky, Commitlint, and GitHub Actions CI out of the box
40
40
 
41
41
  ## Features
42
42
 
@@ -48,7 +48,7 @@ Production-grade Next.js starter with feature-sliced architecture, strict TypeSc
48
48
  - 🔄 **TanStack Query** · **Zustand** · **nuqs**
49
49
  - 📋 **React Hook Form** + **Zod** — validated forms end to end
50
50
  - 🤖 **ViraStack AI** — agent context, scoped rules, design skills
51
- - ✅ **Quality built in** — ESLint, Prettier, Knip, Husky, Commitlint, CI (typecheck → lint → knip → build)
51
+ - ✅ **Quality built in** — ESLint, Prettier, Knip, Husky, Commitlint, Vitest + Testing Library + MSW, CI (typecheck → lint → knip → test → build)
52
52
 
53
53
  **Optional / bring your own**
54
54
 
@@ -89,8 +89,9 @@ Validated in `src/env.ts`. Scaffolded projects already have `.env.local`.
89
89
  | `format` / `format:check` | Prettier |
90
90
  | `typecheck` | `tsc --noEmit` |
91
91
  | `knip` | Find unused files, exports, and dependencies |
92
+ | `test` / `test:watch` | Vitest (single run / watch mode) |
92
93
 
93
- CI runs the same gates: typecheck → lint → knip → build (see `.github/workflows/ci.yml`).
94
+ CI runs the same gates: typecheck → lint → knip → test → build (see `.github/workflows/ci.yml`).
94
95
 
95
96
  ## Project structure
96
97
 
@@ -102,7 +103,6 @@ src/
102
103
  │ ├── api/
103
104
  │ ├── components/ # Hero, Features, Showcase, demos, LandingPage
104
105
  │ ├── constants/
105
- │ ├── data/
106
106
  │ ├── helpers/
107
107
  │ ├── hooks/
108
108
  │ ├── icons/ # Brand SVGs used only by the landing demo
@@ -124,6 +124,7 @@ src/
124
124
  ├── helpers/
125
125
  ├── types/
126
126
  ├── styles/
127
+ ├── test/ # Vitest setup, MSW server, renderWithProviders
127
128
  └── env.ts
128
129
  ```
129
130
 
@@ -140,8 +141,10 @@ The bundled `landing` feature follows the ViraStack AI feature tree and is meant
140
141
  - Default scope is `features/[feature]/` — promote to global `src/` only when a second feature needs it
141
142
  - No cross-feature imports; share via `components/`, `lib/`, `hooks/`, etc.
142
143
  - Path aliases are mandatory; parent-relative imports (`../`) are forbidden
144
+ - ESLint enforces both rules (`no-restricted-imports` in the ESLint config)
145
+ - Tests live next to the code they cover (`*.test.ts(x)`)
143
146
 
144
- See [`docs/architecture-guide.md`](docs/architecture-guide.md) for placement rules and import conventions.
147
+ See the ViraStack AI `architecture` rule (`.claude/rules/virastack/` or `.cursor/rules/virastack/`) for placement rules and import conventions.
145
148
 
146
149
  ## Customization
147
150
 
@@ -161,15 +164,18 @@ You own every file — trim demos, rename features, keep only what you need.
161
164
 
162
165
  Developed with [**ViraStack AI**](https://github.com/virastack/ai) — an AI-native architecture kit that ships agent context into every project:
163
166
 
164
- | File | Purpose |
165
- | :---------------- | :------------------------------- |
166
- | `AGENTS.md` | Agent operating guide |
167
- | `CLAUDE.md` | Claude Code entry point |
168
- | `.cursor/rules/` | Scoped coding rules |
169
- | `public/llms.txt` | Machine-readable project summary |
170
- | `.agents/skills/` | Design-taste skills |
171
-
172
- Install or refresh rules: `npx @virastack/ai init`
167
+ | File | Purpose |
168
+ | :------------------------- | :-------------------------------------------- |
169
+ | `CLAUDE.md` | Claude Code entry point (imports `AGENTS.md`) |
170
+ | `.claude/rules/virastack/` | Scoped rules for Claude Code |
171
+ | `.claude/skills/` | Skills for Claude Code |
172
+ | `.cursor/rules/virastack/` | Scoped rules for Cursor |
173
+ | `AGENTS.md` | Agent operating guide (managed block + yours) |
174
+ | `.agents/skills/` | Skills for other agents |
175
+ | `.virastack/ai.json` | Manifest of the files ViraStack AI manages |
176
+ | `public/llms.txt` | Machine-readable project summary |
177
+
178
+ Install or refresh rules: `npx @virastack/ai init` (pick tools with `--agent claude,cursor`).
173
179
 
174
180
  ## Deployment
175
181
 
@@ -1,7 +1,47 @@
1
+ import { existsSync, readdirSync } from "node:fs";
2
+
1
3
  import nextVitals from "eslint-config-next/core-web-vitals";
2
4
  import nextTs from "eslint-config-next/typescript";
3
5
  import { defineConfig, globalIgnores } from "eslint/config";
4
6
 
7
+ const PARENT_IMPORT = {
8
+ group: ["../*"],
9
+ message: "Use a path alias (@/...) instead of a parent-relative import.",
10
+ };
11
+
12
+ // Feature boundaries: a feature may import itself, never another feature.
13
+ const featuresDir = new URL("./src/features", import.meta.url);
14
+ const features = existsSync(featuresDir)
15
+ ? readdirSync(featuresDir, { withFileTypes: true })
16
+ .filter((entry) => entry.isDirectory())
17
+ .map((entry) => entry.name)
18
+ : [];
19
+
20
+ const boundaries = [
21
+ {
22
+ files: ["src/**/*.{ts,tsx}"],
23
+ rules: { "no-restricted-imports": ["error", { patterns: [PARENT_IMPORT] }] },
24
+ },
25
+ ...features.map((feature) => ({
26
+ files: [`src/features/${feature}/**/*.{ts,tsx}`],
27
+ rules: {
28
+ "no-restricted-imports": [
29
+ "error",
30
+ {
31
+ patterns: [
32
+ PARENT_IMPORT,
33
+ {
34
+ group: ["@/features/*", `!@/features/${feature}`, `!@/features/${feature}/**`],
35
+ message:
36
+ "Features must not import each other. Move shared code to src/ (components, lib, hooks, ...).",
37
+ },
38
+ ],
39
+ },
40
+ ],
41
+ },
42
+ })),
43
+ ];
44
+
5
45
  const eslintConfig = defineConfig([
6
46
  ...nextVitals,
7
47
  ...nextTs,
@@ -14,6 +54,7 @@ const eslintConfig = defineConfig([
14
54
  "no-console": ["warn", { allow: ["warn", "error"] }],
15
55
  },
16
56
  },
57
+ ...boundaries,
17
58
  // Override default ignores of eslint-config-next.
18
59
  globalIgnores([
19
60
  // Default ignores of eslint-config-next:
@@ -14,6 +14,8 @@ const config = {
14
14
  "src/constants/index.ts",
15
15
  ],
16
16
  ignoreDependencies: ["tailwindcss", "tw-animate-css"],
17
+ // next-env.d.ts imports route types that exist only after `next dev` or `next build`.
18
+ ignoreUnresolved: [/^\.\/\.next\//],
17
19
  rules: {
18
20
  dependencies: "error",
19
21
  exports: "warn",
@@ -2,7 +2,7 @@
2
2
  "name": "virastack",
3
3
  "version": "1.0.0",
4
4
  "private": true,
5
- "description": "Premium Next.js 16 + React 19 + Tailwind CSS 4 boilerplate focused on clean architecture, UI/UX, and agent-ready developer experience.",
5
+ "description": "Next.js 16 + React 19 + Tailwind CSS 4 boilerplate focused on clean architecture, UI/UX, and agent-ready developer experience.",
6
6
  "author": {
7
7
  "name": "Ömer Gülçiçek (ViraStack)",
8
8
  "email": "iletisim@omergulcicek.com",
@@ -38,6 +38,8 @@
38
38
  "lint:fix": "eslint . --fix --cache && prettier --write .",
39
39
  "lint:ci": "eslint . --max-warnings=0 && prettier --check .",
40
40
  "typecheck": "tsc --noEmit",
41
+ "test": "vitest run",
42
+ "test:watch": "vitest",
41
43
  "prepare": "husky",
42
44
  "changeset": "changeset",
43
45
  "changeset:version": "changeset version",
@@ -75,8 +77,8 @@
75
77
  "@tanstack/react-query-devtools": "^5.101.4",
76
78
  "class-variance-authority": "^0.7.1",
77
79
  "clsx": "^2.1.1",
78
- "framer-motion": "^12.42.2",
79
80
  "lucide-react": "^1.25.0",
81
+ "motion": "^12.43.0",
80
82
  "next": "16.2.11",
81
83
  "next-themes": "^0.4.6",
82
84
  "nuqs": "^2.9.1",
@@ -95,21 +97,30 @@
95
97
  "@commitlint/config-conventional": "^21.2.0",
96
98
  "@ianvs/prettier-plugin-sort-imports": "^4.7.1",
97
99
  "@tailwindcss/postcss": "^4",
100
+ "@testing-library/dom": "^10.4.2",
101
+ "@testing-library/jest-dom": "^6.10.0",
102
+ "@testing-library/react": "^16.3.3",
103
+ "@testing-library/user-event": "^14.6.8",
98
104
  "@types/node": "^20",
99
105
  "@types/react": "^19",
100
106
  "@types/react-dom": "^19",
107
+ "@vitejs/plugin-react": "^6.0.1",
101
108
  "babel-plugin-react-compiler": "1.0.0",
102
109
  "cross-env": "^10.1.0",
103
110
  "eslint": "^9",
104
111
  "eslint-config-next": "16.2.11",
105
112
  "husky": "^9.1.7",
113
+ "jsdom": "^27.4.0",
106
114
  "knip": "^6.28.0",
107
115
  "lint-staged": "^17.1.1",
116
+ "msw": "^2.15.0",
108
117
  "prettier": "^3.9.6",
109
118
  "prettier-plugin-tailwindcss": "^0.8.1",
110
119
  "rimraf": "^6.1.3",
111
120
  "tailwindcss": "^4",
112
121
  "tw-animate-css": "^1.4.0",
113
- "typescript": "^5"
122
+ "typescript": "^6.0.3",
123
+ "vite": "^8.0.0",
124
+ "vitest": "^4.1.12"
114
125
  }
115
126
  }
@@ -1,3 +1,4 @@
1
1
  ignoredBuiltDependencies:
2
+ - msw
2
3
  - sharp
3
4
  - unrs-resolver
@@ -1,24 +1,25 @@
1
1
  # ViraStack Start — Next.js Edition
2
2
 
3
- > Premium Next.js 16 + React 19 + Tailwind CSS 4 boilerplate focused on clean architecture, UI/UX, and agent-ready developer experience. Auth, database/ORM, i18n, and testing are intentionally omitted — bring the solution that fits your project.
3
+ > Next.js 16 + React 19 + Tailwind CSS 4 boilerplate focused on clean architecture, UI/UX, and agent-ready developer experience. Auth, database/ORM, and i18n are intentionally omitted — bring the solution that fits your project.
4
4
 
5
5
  Built on the App Router with Server/Client Components, streaming, and the Metadata API. UI primitives use **Base UI** (unstyled, accessible) styled with Tailwind CSS 4 design tokens. State is split by job: TanStack Query for server state, Zustand for client state, `nuqs` for URL state. Forms use React Hook Form + Zod. The home route renders a deletable `features/landing` demo that follows the canonical ViraStack feature tree. **ViraStack AI** (`@virastack/ai`) adds agent rules on scaffold; design skills come from [emilkowalski/skills](https://github.com/emilkowalski/skills) and [make-interfaces-feel-better](https://github.com/jakubkrehel/make-interfaces-feel-better).
6
6
 
7
- - Tech stack: Next.js 16, React 19, Tailwind CSS 4, TypeScript 5 (strict + `noUncheckedIndexedAccess`), Node.js `>=20.9`
8
- - UI: Base UI, Framer Motion, Sonner, Lucide React, `next-themes`, `class-variance-authority`
7
+ - Tech stack: Next.js 16, React 19, Tailwind CSS 4, TypeScript 6 (strict + `noUncheckedIndexedAccess`), Node.js `>=20.9`
8
+ - UI: Base UI, Motion, Sonner, Lucide React, `next-themes`, `class-variance-authority`
9
9
  - Data & state: TanStack Query 5, Zustand, `nuqs`
10
10
  - Forms: React Hook Form + Zod (+ `@hookform/resolvers`)
11
+ - Testing: Vitest, React Testing Library, user-event, jest-dom, MSW (`src/test/`)
11
12
  - API: Native `fetch` wrapper in `src/lib/api.ts` (`ApiError` with `message`, `status`, `data`); Axios is optional, not installed
12
13
  - Env: Hand-rolled Zod schema in `src/env.ts` (no `@t3-oss/env-nextjs`)
13
14
  - Tooling: ESLint 9, Prettier 3, Husky, Knip, Commitlint, Changesets, `@next/bundle-analyzer`
14
- - Quality scripts: `pnpm typecheck`, `pnpm lint` / `lint:ci`, `pnpm knip`, `pnpm build`
15
- - ViraStack AI (via `npx @virastack/ai init`): `AGENTS.md`, `CLAUDE.md`, `.cursor/rules/*.mdc`, `docs/*`
16
- - Not included by default: Auth, DB/ORM, i18n (`next-intl` not installed), test runner, CSP
15
+ - Quality scripts: `pnpm typecheck`, `pnpm lint` / `lint:ci`, `pnpm knip`, `pnpm test`, `pnpm build`
16
+ - ViraStack AI (via `npx @virastack/ai init`): `AGENTS.md`, `CLAUDE.md`, `.claude/rules/virastack/`, `.cursor/rules/virastack/`, `.virastack/ai.json`
17
+ - Not included by default: Auth, DB/ORM, i18n (`next-intl` not installed), CSP
17
18
 
18
19
  ## Docs
19
20
  - [Repository (GitHub)](https://github.com/virastack/start): CLI and templates
20
21
  - [README](https://github.com/virastack/start/tree/main/templates/nextjs): Features, structure, and conventions
21
- - [Architecture guide](https://github.com/virastack/ai/blob/main/templates/core/docs/architecture-guide.md): Placement rules and import conventions
22
+ - [Architecture guide](https://github.com/virastack/ai/blob/main/templates/rules/architecture.mdc): Placement rules and import conventions
22
23
  - [Product docs](https://virastack.com/start/docs): ViraStack Start documentation
23
24
  - [Start llms-full.txt](https://virastack.com/start/llms-full.txt): CLI flags and template map for agents
24
25
  - [Ecosystem llms.txt](https://virastack.com/llms.txt): All ViraStack products
@@ -57,7 +58,7 @@ Canonical tree under `src/features/landing/`. Home page composes `LandingPage`.
57
58
  - [`src/features/landing/hooks/use-users.ts`](https://github.com/virastack/start/blob/main/templates/nextjs/src/features/landing/hooks/use-users.ts): Feature TanStack Query hook
58
59
  - [`src/features/landing/stores/counter.store.ts`](https://github.com/virastack/start/blob/main/templates/nextjs/src/features/landing/stores/counter.store.ts): Feature Zustand store
59
60
  - [`src/features/landing/schemas/project.schema.ts`](https://github.com/virastack/start/blob/main/templates/nextjs/src/features/landing/schemas/project.schema.ts): Feature Zod schema
60
- - [`src/features/landing/data/features.data.tsx`](https://github.com/virastack/start/blob/main/templates/nextjs/src/features/landing/data/features.data.tsx): Feature static data
61
+ - [`src/features/landing/constants/features.constants.tsx`](https://github.com/virastack/start/blob/main/templates/nextjs/src/features/landing/constants/features.constants.tsx): Feature static lists
61
62
  - [`src/features/landing/helpers/`](https://github.com/virastack/start/tree/main/templates/nextjs/src/features/landing/helpers): Feature helpers (motion tokens, initials)
62
63
  - [`src/features/landing/icons/`](https://github.com/virastack/start/tree/main/templates/nextjs/src/features/landing/icons): Brand SVGs used only by the landing demo
63
64
  - [`src/features/landing/types/user.types.ts`](https://github.com/virastack/start/blob/main/templates/nextjs/src/features/landing/types/user.types.ts): Feature types
@@ -79,11 +80,12 @@ Quick start: delete `src/features/landing`, replace `src/app/page.tsx`, keep `co
79
80
  - Default scope is `features/[feature]/` — promote to global `src/` only when a second feature needs it. No cross-feature imports.
80
81
 
81
82
  ## ViraStack AI
82
- - Install or refresh: `npx @virastack/ai init --force`
83
+ - Install or refresh: `npx @virastack/ai init`
83
84
  - `AGENTS.md`: Agent operating guide (all agents)
84
85
  - `CLAUDE.md`: Claude Code entry point (`@AGENTS.md`)
85
- - `.cursor/rules/*.mdc`: Scoped coding rules (Cursor auto-loads)
86
- - `docs/architecture-guide.md`, `docs/MEMORIES.md`: Architecture and project memory
86
+ - `.claude/rules/virastack/*.md`: Scoped coding rules (Claude Code auto-loads)
87
+ - `.cursor/rules/virastack/*.mdc`: Scoped coding rules (Cursor auto-loads)
88
+ - `.virastack/ai.json`: Installed rule manifest (commit it)
87
89
  - Skills: [emilkowalski/skills](https://github.com/emilkowalski/skills) (7 design-engineering skills) + [make-interfaces-feel-better](https://github.com/jakubkrehel/make-interfaces-feel-better)
88
90
  - Package: [`@virastack/ai`](https://github.com/virastack/ai)
89
91
 
@@ -5,7 +5,7 @@ import { env } from "@/env";
5
5
  export const siteConfig: SiteConfig = {
6
6
  name: env.NEXT_PUBLIC_APP_NAME,
7
7
  description:
8
- "Premium Next.js boilerplate built for scalable UI/UX, clean architecture, and agent-ready developer experience.",
8
+ "Next.js boilerplate built for scalable UI/UX, clean architecture, and agent-ready developer experience.",
9
9
  url: env.NEXT_PUBLIC_APP_URL,
10
10
  ogImage: `${env.NEXT_PUBLIC_APP_URL}/og.png`,
11
11
  links: {
@@ -0,0 +1,34 @@
1
+ import { http, HttpResponse } from "msw";
2
+ import { describe, expect, it } from "vitest";
3
+
4
+ import { ApiError } from "@/lib/api";
5
+
6
+ import { getUsers } from "@/features/landing/api";
7
+ import type { User } from "@/features/landing/types";
8
+ import { server } from "@/test/server";
9
+
10
+ const USERS_URL = "https://jsonplaceholder.typicode.com/users";
11
+
12
+ const users: User[] = [
13
+ {
14
+ id: 1,
15
+ name: "Ada Lovelace",
16
+ username: "ada",
17
+ email: "ada@example.com",
18
+ address: { city: "London" },
19
+ },
20
+ ];
21
+
22
+ describe("getUsers", () => {
23
+ it("returns the users from the API", async () => {
24
+ server.use(http.get(USERS_URL, () => HttpResponse.json(users)));
25
+
26
+ await expect(getUsers()).resolves.toEqual(users);
27
+ });
28
+
29
+ it("throws an ApiError when the API fails", async () => {
30
+ server.use(http.get(USERS_URL, () => HttpResponse.json({ message: "Down" }, { status: 503 })));
31
+
32
+ await expect(getUsers()).rejects.toBeInstanceOf(ApiError);
33
+ });
34
+ });
@@ -1,2 +1,3 @@
1
1
  export { getUsers } from "@/features/landing/api/get-users.api";
2
2
  export { userKeys } from "@/features/landing/api/query-keys";
3
+ export { usersQueryOptions } from "@/features/landing/api/user.queries";
@@ -0,0 +1,10 @@
1
+ import { queryOptions } from "@tanstack/react-query";
2
+
3
+ import { getUsers } from "@/features/landing/api/get-users.api";
4
+ import { userKeys } from "@/features/landing/api/query-keys";
5
+
6
+ export const usersQueryOptions = () =>
7
+ queryOptions({
8
+ queryKey: userKeys.list(),
9
+ queryFn: ({ signal }) => getUsers(signal),
10
+ });
@@ -0,0 +1,22 @@
1
+ import { screen } from "@testing-library/react";
2
+ import userEvent from "@testing-library/user-event";
3
+ import { describe, expect, it } from "vitest";
4
+
5
+ import { CartDemo } from "@/features/landing/components/CartDemo";
6
+ import { renderWithProviders } from "@/test/render";
7
+
8
+ describe("CartDemo", () => {
9
+ it("adds items and enables removing them", async () => {
10
+ const user = userEvent.setup();
11
+ renderWithProviders(<CartDemo />);
12
+
13
+ const decrement = screen.getByRole("button", { name: "Decrement" });
14
+ expect(decrement).toBeDisabled();
15
+
16
+ await user.click(screen.getByRole("button", { name: "Increment" }));
17
+ await user.click(screen.getByRole("button", { name: "Increment" }));
18
+
19
+ expect(screen.getByText("700")).toBeInTheDocument();
20
+ expect(decrement).toBeEnabled();
21
+ });
22
+ });
@@ -1,5 +1,5 @@
1
1
  import { RevealGroup, RevealItem } from "@/features/landing/components/Reveal";
2
- import { FEATURES } from "@/features/landing/data";
2
+ import { FEATURES } from "@/features/landing/constants";
3
3
  import { featureCardClassName } from "@/features/landing/helpers";
4
4
 
5
5
  export function Features() {
@@ -12,7 +12,7 @@ export function Hero() {
12
12
  <section className="mx-auto flex max-w-3xl flex-col items-center gap-6 px-6 pt-24 pb-16 text-center">
13
13
  <Reveal mode="mount" delay={0} y={-8}>
14
14
  <span className="rounded-full border border-border px-3 py-1 text-xs font-medium text-muted-foreground">
15
- Premium UI/UX · Agent-ready · Zero bloat
15
+ UI/UX · Agent-ready · Zero bloat
16
16
  </span>
17
17
  </Reveal>
18
18
 
@@ -49,14 +49,13 @@ export function Hero() {
49
49
 
50
50
  <Reveal mode="mount" delay={0.1} className="mt-2 flex items-center gap-2">
51
51
  <Button
52
- className="h-10 px-4 py-2"
53
52
  nativeButton={false}
54
53
  render={<Link href={siteConfig.links.github} target="_blank" rel="noreferrer" />}
55
54
  >
56
55
  <span>⭐</span>
57
56
  <span>Star on GitHub</span>
58
57
  </Button>
59
- <div className="h-10 w-10 [&>button]:h-full [&>button]:w-full">
58
+ <div className="h-9 w-9 [&>button]:h-full [&>button]:w-full">
60
59
  <ThemeToggle />
61
60
  </div>
62
61
  </Reveal>
@@ -2,7 +2,7 @@
2
2
 
3
3
  import type { ReactNode } from "react";
4
4
 
5
- import { motion } from "framer-motion";
5
+ import { motion } from "motion/react";
6
6
 
7
7
  import { fadeInUp, staggerContainer, viewport } from "@/features/landing/helpers";
8
8
 
@@ -0,0 +1 @@
1
+ export { FEATURES } from "@/features/landing/constants/features.constants";
@@ -0,0 +1,17 @@
1
+ import { describe, expect, it } from "vitest";
2
+
3
+ import { getInitials } from "@/features/landing/helpers/get-initials";
4
+
5
+ describe("getInitials", () => {
6
+ it("uses the first and last name", () => {
7
+ expect(getInitials("Ada King Lovelace")).toBe("AL");
8
+ });
9
+
10
+ it("uses one letter for a single name", () => {
11
+ expect(getInitials(" grace ")).toBe("G");
12
+ });
13
+
14
+ it("falls back to a question mark for an empty name", () => {
15
+ expect(getInitials(" ")).toBe("?");
16
+ });
17
+ });
@@ -2,15 +2,12 @@
2
2
 
3
3
  import { useQuery } from "@tanstack/react-query";
4
4
 
5
- import { getUsers, userKeys } from "@/features/landing/api";
5
+ import { usersQueryOptions } from "@/features/landing/api";
6
6
 
7
7
  /**
8
8
  * Example TanStack Query hook. Lives with the landing demo feature —
9
9
  * delete `src/features/landing` when you start your real product.
10
10
  */
11
11
  export function useUsers() {
12
- return useQuery({
13
- queryKey: userKeys.list(),
14
- queryFn: ({ signal }) => getUsers(signal),
15
- });
12
+ return useQuery(usersQueryOptions());
16
13
  }