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.
- package/README.md +29 -17
- package/package.json +5 -4
- package/src/commands/add.js +12 -5
- package/src/commands/init.js +68 -32
- package/src/i18n.js +36 -20
- package/src/index.js +15 -1
- package/src/utils/agents.js +85 -0
- package/src/utils/args.js +1 -1
- package/src/utils/git.js +18 -4
- package/src/utils/next-agents-md.js +40 -0
- package/src/utils/package-manager.js +5 -2
- package/src/utils/tools.js +1 -1
- package/templates/nextjs/.github/workflows/ci.yml +3 -0
- package/templates/nextjs/.prettierignore +9 -0
- package/templates/nextjs/.prettierrc +0 -1
- package/templates/nextjs/README.md +21 -15
- package/templates/nextjs/eslint.config.mjs +41 -0
- package/templates/nextjs/knip.config.js +2 -0
- package/templates/nextjs/package.json +14 -3
- package/templates/nextjs/pnpm-workspace.yaml +1 -0
- package/templates/nextjs/public/llms.txt +13 -11
- package/templates/nextjs/src/config/site.config.ts +1 -1
- package/templates/nextjs/src/features/landing/api/get-users.api.test.ts +34 -0
- package/templates/nextjs/src/features/landing/api/index.ts +1 -0
- package/templates/nextjs/src/features/landing/api/user.queries.ts +10 -0
- package/templates/nextjs/src/features/landing/components/CartDemo.test.tsx +22 -0
- package/templates/nextjs/src/features/landing/components/Features.tsx +1 -1
- package/templates/nextjs/src/features/landing/components/Hero.tsx +2 -3
- package/templates/nextjs/src/features/landing/components/Reveal.tsx +1 -1
- package/templates/nextjs/src/features/landing/constants/index.ts +1 -0
- package/templates/nextjs/src/features/landing/helpers/get-initials.test.ts +17 -0
- package/templates/nextjs/src/features/landing/hooks/use-users.ts +2 -5
- package/templates/nextjs/src/features/landing/icons/tanstack.tsx +2 -314
- package/templates/nextjs/src/test/render.tsx +19 -0
- package/templates/nextjs/src/test/server.ts +7 -0
- package/templates/nextjs/src/test/setup.ts +15 -0
- package/templates/nextjs/tsconfig.json +0 -2
- package/templates/nextjs/vitest.config.ts +13 -0
- package/templates/tanstack/.github/workflows/ci.yml +3 -0
- package/templates/tanstack/.prettierignore +9 -0
- package/templates/tanstack/.prettierrc +0 -1
- package/templates/tanstack/README.md +21 -15
- package/templates/tanstack/eslint.config.js +47 -0
- package/templates/tanstack/package.json +12 -3
- package/templates/tanstack/pnpm-workspace.yaml +1 -0
- package/templates/tanstack/public/llms.txt +12 -10
- package/templates/tanstack/src/config/site.config.ts +1 -1
- package/templates/tanstack/src/features/landing/api/get-users.api.test.ts +34 -0
- package/templates/tanstack/src/features/landing/api/index.ts +1 -0
- package/templates/tanstack/src/features/landing/api/user.queries.ts +10 -0
- package/templates/tanstack/src/features/landing/components/CartDemo.test.tsx +22 -0
- package/templates/tanstack/src/features/landing/components/Features.tsx +1 -1
- package/templates/tanstack/src/features/landing/components/Hero.tsx +2 -3
- package/templates/tanstack/src/features/landing/components/Reveal.tsx +1 -1
- package/templates/tanstack/src/features/landing/components/UsersDemo.tsx +1 -1
- package/templates/tanstack/src/features/landing/constants/index.ts +1 -0
- package/templates/tanstack/src/features/landing/helpers/get-initials.test.ts +17 -0
- package/templates/tanstack/src/features/landing/hooks/use-users.ts +3 -6
- package/templates/tanstack/src/features/landing/icons/tanstack.tsx +2 -314
- package/templates/tanstack/src/test/render.tsx +19 -0
- package/templates/tanstack/src/test/server.ts +7 -0
- package/templates/tanstack/src/test/setup.ts +15 -0
- package/templates/tanstack/vitest.config.ts +13 -0
- package/templates-i18n/nextjs/.github/workflows/ci.yml +3 -0
- package/templates-i18n/nextjs/.prettierignore +9 -0
- package/templates-i18n/nextjs/.prettierrc +0 -1
- package/templates-i18n/nextjs/README.md +21 -15
- package/templates-i18n/nextjs/eslint.config.mjs +41 -0
- package/templates-i18n/nextjs/knip.config.js +4 -2
- package/templates-i18n/nextjs/{src/messages → messages}/en.json +1 -1
- package/templates-i18n/nextjs/{src/messages → messages}/tr.json +1 -1
- package/templates-i18n/nextjs/package.json +14 -3
- package/templates-i18n/nextjs/pnpm-workspace.yaml +1 -0
- package/templates-i18n/nextjs/public/llms.txt +11 -10
- package/templates-i18n/nextjs/src/components/shared/LanguageSwitcher.tsx +2 -2
- package/templates-i18n/nextjs/src/config/site.config.ts +1 -1
- package/templates-i18n/nextjs/src/features/landing/api/get-users.api.test.ts +34 -0
- package/templates-i18n/nextjs/src/features/landing/api/index.ts +1 -0
- package/templates-i18n/nextjs/src/features/landing/api/user.queries.ts +10 -0
- package/templates-i18n/nextjs/src/features/landing/components/CartDemo.test.tsx +22 -0
- package/templates-i18n/nextjs/src/features/landing/components/Hero.tsx +1 -2
- package/templates-i18n/nextjs/src/features/landing/components/Reveal.tsx +1 -1
- package/templates-i18n/nextjs/src/features/landing/constants/index.ts +1 -0
- package/templates-i18n/nextjs/src/features/landing/helpers/get-initials.test.ts +17 -0
- package/templates-i18n/nextjs/src/features/landing/hooks/use-users.ts +2 -5
- package/templates-i18n/nextjs/src/features/landing/icons/tanstack.tsx +2 -314
- package/templates-i18n/nextjs/src/i18n/request.ts +1 -1
- package/templates-i18n/nextjs/src/test/render.tsx +26 -0
- package/templates-i18n/nextjs/src/test/server.ts +7 -0
- package/templates-i18n/nextjs/src/test/setup.ts +15 -0
- package/templates-i18n/nextjs/tsconfig.json +1 -2
- package/templates-i18n/nextjs/vitest.config.ts +13 -0
- package/templates-i18n/tanstack/.github/workflows/ci.yml +3 -0
- package/templates-i18n/tanstack/.prettierignore +12 -0
- package/templates-i18n/tanstack/.prettierrc +0 -1
- package/templates-i18n/tanstack/README.md +21 -15
- package/templates-i18n/tanstack/_gitignore +3 -0
- package/templates-i18n/tanstack/eslint.config.js +47 -0
- package/templates-i18n/tanstack/knip.config.js +2 -2
- package/templates-i18n/tanstack/messages/en.json +1 -1
- package/templates-i18n/tanstack/messages/tr.json +1 -1
- package/templates-i18n/tanstack/package.json +14 -4
- package/templates-i18n/tanstack/pnpm-workspace.yaml +1 -0
- package/templates-i18n/tanstack/public/llms.txt +7 -6
- package/templates-i18n/tanstack/src/components/shared/LanguageSwitcher.tsx +2 -2
- package/templates-i18n/tanstack/src/config/site.config.ts +1 -1
- package/templates-i18n/tanstack/src/features/landing/api/get-users.api.test.ts +34 -0
- package/templates-i18n/tanstack/src/features/landing/api/index.ts +1 -0
- package/templates-i18n/tanstack/src/features/landing/api/user.queries.ts +10 -0
- package/templates-i18n/tanstack/src/features/landing/components/CartDemo.test.tsx +22 -0
- package/templates-i18n/tanstack/src/features/landing/components/Hero.tsx +1 -2
- package/templates-i18n/tanstack/src/features/landing/components/Reveal.tsx +1 -1
- package/templates-i18n/tanstack/src/features/landing/constants/index.ts +1 -0
- package/templates-i18n/tanstack/src/features/landing/helpers/get-initials.test.ts +17 -0
- package/templates-i18n/tanstack/src/features/landing/hooks/use-users.ts +3 -6
- package/templates-i18n/tanstack/src/features/landing/icons/tanstack.tsx +2 -314
- package/templates-i18n/tanstack/src/test/render.tsx +19 -0
- package/templates-i18n/tanstack/src/test/server.ts +7 -0
- package/templates-i18n/tanstack/src/test/setup.ts +15 -0
- package/templates-i18n/tanstack/vitest.config.ts +13 -0
- package/templates/nextjs/.eslintcache +0 -1
- package/templates/nextjs/src/features/landing/data/index.ts +0 -1
- package/templates/tanstack/.eslintcache +0 -1
- package/templates/tanstack/src/features/landing/data/index.ts +0 -1
- package/templates-i18n/nextjs/.eslintcache +0 -1
- package/templates-i18n/nextjs/src/features/landing/data/index.ts +0 -1
- package/templates-i18n/tanstack/.eslintcache +0 -1
- package/templates-i18n/tanstack/src/features/landing/data/index.ts +0 -1
- /package/templates/nextjs/src/features/landing/{data/features.data.tsx → constants/features.constants.tsx} +0 -0
- /package/templates/tanstack/src/features/landing/{data/features.data.tsx → constants/features.constants.tsx} +0 -0
- /package/templates-i18n/nextjs/src/features/landing/{data/features.data.tsx → constants/features.constants.tsx} +0 -0
- /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
|
|
14
|
-
*
|
|
15
|
-
*
|
|
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
|
-
|
|
32
|
-
|
|
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
|
}
|
package/src/utils/tools.js
CHANGED
|
@@ -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
|
|
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 = [
|
|
@@ -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
|
|
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
|
|
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
|
|
165
|
-
|
|
|
166
|
-
| `
|
|
167
|
-
|
|
|
168
|
-
| `.
|
|
169
|
-
|
|
|
170
|
-
|
|
|
171
|
-
|
|
172
|
-
|
|
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": "
|
|
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": "^
|
|
122
|
+
"typescript": "^6.0.3",
|
|
123
|
+
"vite": "^8.0.0",
|
|
124
|
+
"vitest": "^4.1.12"
|
|
114
125
|
}
|
|
115
126
|
}
|
|
@@ -1,24 +1,25 @@
|
|
|
1
1
|
# ViraStack Start — Next.js Edition
|
|
2
2
|
|
|
3
|
-
>
|
|
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
|
|
8
|
-
- UI: Base UI,
|
|
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
|
|
16
|
-
- Not included by default: Auth, DB/ORM, i18n (`next-intl` not installed),
|
|
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/
|
|
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/
|
|
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
|
|
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
|
-
- `.
|
|
86
|
-
-
|
|
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
|
-
"
|
|
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
|
+
});
|
|
@@ -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/
|
|
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
|
-
|
|
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-
|
|
58
|
+
<div className="h-9 w-9 [&>button]:h-full [&>button]:w-full">
|
|
60
59
|
<ThemeToggle />
|
|
61
60
|
</div>
|
|
62
61
|
</Reveal>
|
|
@@ -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 {
|
|
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
|
}
|