create-veap 0.1.2
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 +3 -0
- package/bin/veap.js +7 -0
- package/dist/commands/docker.d.ts +1 -0
- package/dist/commands/docker.js +55 -0
- package/dist/commands/init.d.ts +5 -0
- package/dist/commands/init.js +111 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +16 -0
- package/dist/main.d.ts +2 -0
- package/dist/main.js +1 -0
- package/dist/manifests.d.ts +22 -0
- package/dist/manifests.js +32 -0
- package/dist/mutate.d.ts +46 -0
- package/dist/mutate.js +127 -0
- package/dist/package-manager.d.ts +56 -0
- package/dist/package-manager.js +222 -0
- package/dist/project-name.d.ts +22 -0
- package/dist/project-name.js +107 -0
- package/dist/prompts.d.ts +20 -0
- package/dist/prompts.js +93 -0
- package/dist/scaffold.d.ts +26 -0
- package/dist/scaffold.js +62 -0
- package/dist/utils.d.ts +1 -0
- package/dist/utils.js +29 -0
- package/package.json +45 -0
- package/stubs/docker/.dockerignore.stub +39 -0
- package/stubs/docker/Dockerfile.bun.stub +42 -0
- package/stubs/docker/Dockerfile.npm.stub +42 -0
- package/stubs/docker/Dockerfile.stub +49 -0
- package/stubs/docker/Dockerfile.yarn.stub +44 -0
- package/stubs/docker/compose.yml.stub +58 -0
- package/stubs/overlay-full/AGENTS.md.stub +63 -0
- package/stubs/overlay-full/app/[[...catchAll]]/page.tsx.stub +47 -0
- package/stubs/overlay-full/app/api/[...catchAll]/route.ts.stub +111 -0
- package/stubs/overlay-full/app/error.tsx.stub +34 -0
- package/stubs/overlay-full/app/globals.css.stub +124 -0
- package/stubs/overlay-full/app/layout.tsx.stub +71 -0
- package/stubs/overlay-full/app/not-found.tsx.stub +29 -0
- package/stubs/overlay-full/app/storage/[...path]/route.ts.stub +61 -0
- package/stubs/overlay-full/lib/plugins.gen.ts.stub +6 -0
- package/stubs/overlay-full/lib/veap.ts.stub +26 -0
- package/stubs/overlay-full/migrations/index.ts.stub +2 -0
- package/stubs/overlay-full/next.config.ts.stub +34 -0
- package/stubs/overlay-full/proxy.ts.stub +27 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Stage 1: Base image (bun)
|
|
2
|
+
FROM node:22-alpine AS base
|
|
3
|
+
RUN apk add --no-cache libc6-compat
|
|
4
|
+
|
|
5
|
+
# Install bun (Node stays the runtime for the standalone server)
|
|
6
|
+
RUN npm install -g bun
|
|
7
|
+
|
|
8
|
+
# Stage 2: Install dependencies
|
|
9
|
+
FROM base AS deps
|
|
10
|
+
WORKDIR /app
|
|
11
|
+
COPY . .
|
|
12
|
+
RUN bun install --frozen-lockfile
|
|
13
|
+
|
|
14
|
+
# Stage 3: Build the project
|
|
15
|
+
FROM base AS builder
|
|
16
|
+
WORKDIR /app
|
|
17
|
+
COPY --from=deps /app ./
|
|
18
|
+
RUN bun run build
|
|
19
|
+
|
|
20
|
+
# Stage 4: Production runner
|
|
21
|
+
FROM base AS runner
|
|
22
|
+
WORKDIR /app
|
|
23
|
+
|
|
24
|
+
ENV NODE_ENV=production
|
|
25
|
+
ENV NEXT_TELEMETRY_DISABLED=1
|
|
26
|
+
|
|
27
|
+
RUN addgroup --system --gid 1001 nodejs
|
|
28
|
+
RUN adduser --system --uid 1001 nextjs
|
|
29
|
+
|
|
30
|
+
# Automatically leverage output traces to reduce image size
|
|
31
|
+
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
|
|
32
|
+
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
|
|
33
|
+
COPY --from=builder --chown=nextjs:nodejs /app/public ./public
|
|
34
|
+
|
|
35
|
+
USER nextjs
|
|
36
|
+
|
|
37
|
+
EXPOSE 3000
|
|
38
|
+
|
|
39
|
+
ENV PORT=3000
|
|
40
|
+
ENV HOSTNAME="0.0.0.0"
|
|
41
|
+
|
|
42
|
+
CMD ["node", "./server.js"]
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Stage 1: Base image (npm)
|
|
2
|
+
FROM node:22-alpine AS base
|
|
3
|
+
RUN apk add --no-cache libc6-compat
|
|
4
|
+
|
|
5
|
+
# Stage 2: Install dependencies
|
|
6
|
+
FROM base AS deps
|
|
7
|
+
WORKDIR /app
|
|
8
|
+
COPY . .
|
|
9
|
+
RUN npm config set fetch-retries 10 && \
|
|
10
|
+
npm config set fetch-retry-maxtimeout 120000 && \
|
|
11
|
+
npm config set fetch-timeout 600000 && \
|
|
12
|
+
npm ci
|
|
13
|
+
|
|
14
|
+
# Stage 3: Build the project
|
|
15
|
+
FROM base AS builder
|
|
16
|
+
WORKDIR /app
|
|
17
|
+
COPY --from=deps /app ./
|
|
18
|
+
RUN npm run build
|
|
19
|
+
|
|
20
|
+
# Stage 4: Production runner
|
|
21
|
+
FROM base AS runner
|
|
22
|
+
WORKDIR /app
|
|
23
|
+
|
|
24
|
+
ENV NODE_ENV=production
|
|
25
|
+
ENV NEXT_TELEMETRY_DISABLED=1
|
|
26
|
+
|
|
27
|
+
RUN addgroup --system --gid 1001 nodejs
|
|
28
|
+
RUN adduser --system --uid 1001 nextjs
|
|
29
|
+
|
|
30
|
+
# Automatically leverage output traces to reduce image size
|
|
31
|
+
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
|
|
32
|
+
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
|
|
33
|
+
COPY --from=builder --chown=nextjs:nodejs /app/public ./public
|
|
34
|
+
|
|
35
|
+
USER nextjs
|
|
36
|
+
|
|
37
|
+
EXPOSE 3000
|
|
38
|
+
|
|
39
|
+
ENV PORT=3000
|
|
40
|
+
ENV HOSTNAME="0.0.0.0"
|
|
41
|
+
|
|
42
|
+
CMD ["node", "./server.js"]
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Stage 1: Base image with pnpm
|
|
2
|
+
FROM node:22-alpine AS base
|
|
3
|
+
RUN apk add --no-cache libc6-compat
|
|
4
|
+
ENV PNPM_HOME="/pnpm"
|
|
5
|
+
ENV PATH="$PNPM_HOME:$PATH"
|
|
6
|
+
|
|
7
|
+
RUN corepack enable
|
|
8
|
+
RUN corepack prepare pnpm@10.29.3 --activate
|
|
9
|
+
|
|
10
|
+
# Stage 2: Install dependencies
|
|
11
|
+
FROM base AS deps
|
|
12
|
+
WORKDIR /app
|
|
13
|
+
COPY . .
|
|
14
|
+
RUN --mount=type=cache,id=pnpm,target=/pnpm/store \
|
|
15
|
+
pnpm config set store-dir /pnpm/store && \
|
|
16
|
+
pnpm config set fetch-retries 10 && \
|
|
17
|
+
pnpm config set fetch-retry-maxtimeout 120000 && \
|
|
18
|
+
pnpm config set fetch-timeout 600000 && \
|
|
19
|
+
pnpm install --frozen-lockfile
|
|
20
|
+
|
|
21
|
+
# Stage 3: Build the project
|
|
22
|
+
FROM base AS builder
|
|
23
|
+
WORKDIR /app
|
|
24
|
+
COPY --from=deps /app ./
|
|
25
|
+
RUN pnpm build
|
|
26
|
+
|
|
27
|
+
# Stage 4: Production runner
|
|
28
|
+
FROM base AS runner
|
|
29
|
+
WORKDIR /app
|
|
30
|
+
|
|
31
|
+
ENV NODE_ENV=production
|
|
32
|
+
ENV NEXT_TELEMETRY_DISABLED=1
|
|
33
|
+
|
|
34
|
+
RUN addgroup --system --gid 1001 nodejs
|
|
35
|
+
RUN adduser --system --uid 1001 nextjs
|
|
36
|
+
|
|
37
|
+
# Automatically leverage output traces to reduce image size
|
|
38
|
+
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
|
|
39
|
+
COPY --from=builder --chown=nextjs:nodejs /app/.next/static .next/static
|
|
40
|
+
COPY --from=builder --chown=nextjs:nodejs /app/public public
|
|
41
|
+
|
|
42
|
+
USER nextjs
|
|
43
|
+
|
|
44
|
+
EXPOSE 3000
|
|
45
|
+
|
|
46
|
+
ENV PORT=3000
|
|
47
|
+
ENV HOSTNAME="0.0.0.0"
|
|
48
|
+
|
|
49
|
+
CMD ["node", "./server.js"]
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Stage 1: Base image (yarn berry)
|
|
2
|
+
FROM node:22-alpine AS base
|
|
3
|
+
RUN apk add --no-cache libc6-compat
|
|
4
|
+
|
|
5
|
+
RUN corepack enable
|
|
6
|
+
RUN corepack prepare yarn@4.9.2 --activate
|
|
7
|
+
|
|
8
|
+
# Stage 2: Install dependencies
|
|
9
|
+
FROM base AS deps
|
|
10
|
+
WORKDIR /app
|
|
11
|
+
COPY . .
|
|
12
|
+
RUN --mount=type=cache,id=yarn,target=/root/.yarn/berry/cache \
|
|
13
|
+
yarn config set enableNetworkConcurrency 8 && \
|
|
14
|
+
yarn install --immutable
|
|
15
|
+
|
|
16
|
+
# Stage 3: Build the project
|
|
17
|
+
FROM base AS builder
|
|
18
|
+
WORKDIR /app
|
|
19
|
+
COPY --from=deps /app ./
|
|
20
|
+
RUN yarn build
|
|
21
|
+
|
|
22
|
+
# Stage 4: Production runner
|
|
23
|
+
FROM base AS runner
|
|
24
|
+
WORKDIR /app
|
|
25
|
+
|
|
26
|
+
ENV NODE_ENV=production
|
|
27
|
+
ENV NEXT_TELEMETRY_DISABLED=1
|
|
28
|
+
|
|
29
|
+
RUN addgroup --system --gid 1001 nodejs
|
|
30
|
+
RUN adduser --system --uid 1001 nextjs
|
|
31
|
+
|
|
32
|
+
# Automatically leverage output traces to reduce image size
|
|
33
|
+
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
|
|
34
|
+
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
|
|
35
|
+
COPY --from=builder --chown=nextjs:nodejs /app/public ./public
|
|
36
|
+
|
|
37
|
+
USER nextjs
|
|
38
|
+
|
|
39
|
+
EXPOSE 3000
|
|
40
|
+
|
|
41
|
+
ENV PORT=3000
|
|
42
|
+
ENV HOSTNAME="0.0.0.0"
|
|
43
|
+
|
|
44
|
+
CMD ["node", "./server.js"]
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
services:
|
|
2
|
+
# Node.js service (use with: docker compose up {{name}} --build)
|
|
3
|
+
{{name}}:
|
|
4
|
+
build:
|
|
5
|
+
context: .
|
|
6
|
+
dockerfile: Dockerfile
|
|
7
|
+
image: {{name}}-image
|
|
8
|
+
container_name: {{name}}
|
|
9
|
+
env_file:
|
|
10
|
+
- .env
|
|
11
|
+
environment:
|
|
12
|
+
NODE_ENV: production
|
|
13
|
+
PORT: "3000"
|
|
14
|
+
NEXT_TELEMETRY_DISABLED: "1"
|
|
15
|
+
DATABASE_URL: "postgresql://postgres:postgres@database:5432/postgres?sslmode=disable"
|
|
16
|
+
depends_on:
|
|
17
|
+
database:
|
|
18
|
+
condition: service_healthy
|
|
19
|
+
redis:
|
|
20
|
+
condition: service_started
|
|
21
|
+
ports:
|
|
22
|
+
- "80:3000"
|
|
23
|
+
restart: unless-stopped
|
|
24
|
+
volumes:
|
|
25
|
+
- ./storage:/app/public/storage
|
|
26
|
+
|
|
27
|
+
# PostgreSQL service
|
|
28
|
+
database:
|
|
29
|
+
image: postgres:17-alpine
|
|
30
|
+
container_name: {{name}}-db
|
|
31
|
+
restart: always
|
|
32
|
+
environment:
|
|
33
|
+
POSTGRES_USER: postgres
|
|
34
|
+
POSTGRES_PASSWORD: postgres
|
|
35
|
+
POSTGRES_DB: postgres
|
|
36
|
+
ports:
|
|
37
|
+
- "5432:5432"
|
|
38
|
+
volumes:
|
|
39
|
+
- postgres_data:/var/lib/postgresql/data
|
|
40
|
+
healthcheck:
|
|
41
|
+
test: ["CMD-SHELL", "pg_isready -U postgres -d postgres"]
|
|
42
|
+
interval: 5s
|
|
43
|
+
timeout: 5s
|
|
44
|
+
retries: 5
|
|
45
|
+
|
|
46
|
+
# Redis service
|
|
47
|
+
redis:
|
|
48
|
+
image: redis:7-alpine
|
|
49
|
+
container_name: {{name}}-redis
|
|
50
|
+
restart: always
|
|
51
|
+
ports:
|
|
52
|
+
- "6379:6379"
|
|
53
|
+
volumes:
|
|
54
|
+
- redis_data:/data
|
|
55
|
+
|
|
56
|
+
volumes:
|
|
57
|
+
postgres_data:
|
|
58
|
+
redis_data:
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
<!-- BEGIN:veap-agent-rules -->
|
|
2
|
+
|
|
3
|
+
# Veap Application - Agent Instructions
|
|
4
|
+
|
|
5
|
+
This project is a **Veap** application: the official `create-next-app` base plus the Veap overlay (root layout, router, API pipeline, bootstrap). Next.js owns the shell; **Veap owns routing, auth, database, events, plugins and storage**. When the two disagree, prefer the Veap mechanisms below.
|
|
6
|
+
|
|
7
|
+
## Project Structure (Veap-owned files)
|
|
8
|
+
|
|
9
|
+
- `lib/veap.ts` - the composition root: `Application.configure()...create()` + `initializeSystem()` (cached bootstrap). Providers and plugins are wired here.
|
|
10
|
+
- `lib/plugins.gen.ts` - **generated by `veap register`. Never edit by hand.** It statically imports installed plugins so the bundler can trace them.
|
|
11
|
+
- `app/[[...catchAll]]/page.tsx` - every page render goes through `VeapRouter` (route tree built from plugins + app routes). Do not add competing page routes without understanding the router.
|
|
12
|
+
- `app/api/[...catchAll]/route.ts` - API pipeline: middlewares, auth, roles/permissions resolved from the route tree.
|
|
13
|
+
- `app/layout.tsx`, `app/not-found.tsx` - root shell with the bootstrap and the **force-dynamic contract (below)**.
|
|
14
|
+
- `plugins/`, `templates/` - workspace packages (pnpm workspace / npm workspaces).
|
|
15
|
+
- `migrations/` - app migrations (`veap make:migration`).
|
|
16
|
+
|
|
17
|
+
## Critical Contracts (do not break)
|
|
18
|
+
|
|
19
|
+
### 1. `force-dynamic` on the root layout and not-found
|
|
20
|
+
|
|
21
|
+
`app/layout.tsx` and `app/not-found.tsx` **must** keep:
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
export const dynamic = "force-dynamic";
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Why: `Application.bootstrap()` intentionally skips provider boot during `next build` (`NEXT_PHASE=phase-production-build`) - the database and env are not guaranteed at build time. But Next still prerenders `/_not-found` with the full root layout, whose helpers (`getPathPrefix()`, extension points) require booted contexts. Without `force-dynamic` the build fails with `AppError: [Plugins] Context is not bound`. This is a deliberate architectural trade-off (see ADR-006 of the framework): the whole app is dynamic; only `manifest.webmanifest` is static.
|
|
28
|
+
|
|
29
|
+
### 2. `ENCRYPTION_KEY` is required - no fallback
|
|
30
|
+
|
|
31
|
+
`.env` (generated) contains a random 16-byte base64 key used for AES encryption at rest (recovery codes, TOTP). The application **fails fast at boot** if the key is missing or does not decode to exactly 16/24/32 bytes. Regenerate with `openssl rand -base64 16`. Never commit `.env`.
|
|
32
|
+
|
|
33
|
+
### 3. Database safety
|
|
34
|
+
|
|
35
|
+
All mutations go through `transaction()` from `@veap/framework/database` (AsyncLocalStorage-based). Raw knex handles are available for reads, but transactions are the only supported write path.
|
|
36
|
+
|
|
37
|
+
### 4. Plugins and events
|
|
38
|
+
|
|
39
|
+
- Plugins are isolated packages: import only their public exports (`package.json` → `exports`).
|
|
40
|
+
- Use the typed event bus (`system:auth:*` etc.); publish plugin events with your own `plugin:*` prefix.
|
|
41
|
+
- Application services receive dependencies through ports (DI container); never `container.resolve()` inside request code - use the bound contexts/facades (`getCurrentSession`, `sendMail`, ...).
|
|
42
|
+
|
|
43
|
+
### 5. Mail
|
|
44
|
+
|
|
45
|
+
`MAIL_TRANSPORT` selects the delivery: `smtp` (default), `console` (dev - codes are logged), or `custom` (register your own `IMailer` under `CUSTOM_MAILER`). Transports only deliver `MailMessage` DTOs; semantic emails (verification codes, resets) are built as mailables in application code.
|
|
46
|
+
|
|
47
|
+
## Commands
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
bun dev # development server (or pnpm dev / npm run dev)
|
|
51
|
+
bun run build # production build
|
|
52
|
+
bun run lint # eslint
|
|
53
|
+
veap add <plugin> # install + register a plugin
|
|
54
|
+
veap register # regenerate lib/plugins.gen.ts
|
|
55
|
+
veap make:plugin <name>
|
|
56
|
+
veap make:migration <name>
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Source of Truth
|
|
60
|
+
|
|
61
|
+
The installed framework code (`node_modules/@veap/framework`) overrides any documentation, including this file. If this file disagrees with the code, follow the code.
|
|
62
|
+
|
|
63
|
+
<!-- END:veap-agent-rules -->
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { VeapRouter } from "@veap/framework/router";
|
|
2
|
+
import { buildRouteTree } from "@veap/framework/router/server";
|
|
3
|
+
import type { Metadata } from "next";
|
|
4
|
+
import { initializeSystem } from "@/lib/veap";
|
|
5
|
+
|
|
6
|
+
export const dynamic = "force-dynamic";
|
|
7
|
+
|
|
8
|
+
export async function generateMetadata({
|
|
9
|
+
params,
|
|
10
|
+
searchParams,
|
|
11
|
+
}: {
|
|
12
|
+
params: Promise<{ catchAll?: string[] }>;
|
|
13
|
+
searchParams: Promise<Record<string, string>>;
|
|
14
|
+
}): Promise<Metadata> {
|
|
15
|
+
const { catchAll } = await params;
|
|
16
|
+
const sParams = await searchParams;
|
|
17
|
+
const path = catchAll && catchAll.length > 0 ? `/${catchAll.join("/")}` : "/";
|
|
18
|
+
|
|
19
|
+
await initializeSystem();
|
|
20
|
+
const tree = await buildRouteTree(true);
|
|
21
|
+
const result = await tree.generateMetadata(path, sParams);
|
|
22
|
+
|
|
23
|
+
return result;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export default async function CatchAllPage({
|
|
27
|
+
params,
|
|
28
|
+
searchParams,
|
|
29
|
+
}: {
|
|
30
|
+
params: Promise<{ catchAll?: string[] }>;
|
|
31
|
+
searchParams: Promise<any>;
|
|
32
|
+
}) {
|
|
33
|
+
const { catchAll } = await params;
|
|
34
|
+
const sParams = await searchParams;
|
|
35
|
+
const path = catchAll && catchAll.length > 0 ? `/${catchAll.join("/")}` : "/";
|
|
36
|
+
|
|
37
|
+
await initializeSystem();
|
|
38
|
+
const tree = await buildRouteTree(true);
|
|
39
|
+
|
|
40
|
+
const result = await VeapRouter({
|
|
41
|
+
tree,
|
|
42
|
+
path,
|
|
43
|
+
searchParams: sParams,
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
return result;
|
|
47
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { ApiEnsuredAuth, runApiPipeline } from "@veap/framework/router/server";
|
|
2
|
+
import { initializeSystem } from "@/lib/veap";
|
|
3
|
+
|
|
4
|
+
export const dynamic = "force-dynamic";
|
|
5
|
+
|
|
6
|
+
async function handleRequest(
|
|
7
|
+
request: Request,
|
|
8
|
+
context: { params: Promise<{ catchAll: string[] }> },
|
|
9
|
+
) {
|
|
10
|
+
// Ensure system is initialized (DB injected, plugins registered)
|
|
11
|
+
await initializeSystem();
|
|
12
|
+
|
|
13
|
+
const { catchAll } = await context.params;
|
|
14
|
+
const path = `/api/${catchAll.join("/")}`;
|
|
15
|
+
|
|
16
|
+
const { buildRouteTree } = await import("@veap/framework/router/server");
|
|
17
|
+
const tree = await buildRouteTree(true);
|
|
18
|
+
|
|
19
|
+
const match = tree.match(path);
|
|
20
|
+
|
|
21
|
+
const node = match?.node as any;
|
|
22
|
+
|
|
23
|
+
if (match?.isExact && node?.route) {
|
|
24
|
+
const method = request.method.toUpperCase();
|
|
25
|
+
const handler = node.route[method] || node.route.handler;
|
|
26
|
+
|
|
27
|
+
if (handler) {
|
|
28
|
+
const middlewares = [];
|
|
29
|
+
|
|
30
|
+
// Collect middlewares from the layout chain
|
|
31
|
+
for (const entry of match.layoutChain) {
|
|
32
|
+
if (entry.middlewares) {
|
|
33
|
+
middlewares.push(...entry.middlewares);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Node middlewares
|
|
38
|
+
if (node.middlewares) {
|
|
39
|
+
middlewares.push(...node.middlewares);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Resolve auth from chain (closest to leaf wins)
|
|
43
|
+
let authRequired = node.auth;
|
|
44
|
+
if (authRequired === undefined) {
|
|
45
|
+
for (let i = match.layoutChain.length - 1; i >= 0; i--) {
|
|
46
|
+
if (match.layoutChain[i].auth !== undefined) {
|
|
47
|
+
authRequired = match.layoutChain[i].auth;
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Roles and permissions
|
|
54
|
+
let roles = node.roles;
|
|
55
|
+
if (!roles) {
|
|
56
|
+
for (let i = match.layoutChain.length - 1; i >= 0; i--) {
|
|
57
|
+
if (match.layoutChain[i].roles) {
|
|
58
|
+
roles = match.layoutChain[i].roles;
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
let permissions = node.permissions;
|
|
65
|
+
if (!permissions) {
|
|
66
|
+
for (let i = match.layoutChain.length - 1; i >= 0; i--) {
|
|
67
|
+
if (match.layoutChain[i].permissions) {
|
|
68
|
+
permissions = match.layoutChain[i].permissions;
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (authRequired) {
|
|
75
|
+
middlewares.unshift(ApiEnsuredAuth);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return await runApiPipeline(
|
|
79
|
+
middlewares as any,
|
|
80
|
+
request,
|
|
81
|
+
{
|
|
82
|
+
params: match.params,
|
|
83
|
+
roles,
|
|
84
|
+
permissions,
|
|
85
|
+
},
|
|
86
|
+
async (req: Request, ctx: any) => {
|
|
87
|
+
return handler(req, ctx);
|
|
88
|
+
},
|
|
89
|
+
);
|
|
90
|
+
} else {
|
|
91
|
+
return new Response(
|
|
92
|
+
JSON.stringify({ error: `Method ${method} Not Allowed` }),
|
|
93
|
+
{
|
|
94
|
+
status: 405,
|
|
95
|
+
headers: { "Content-Type": "application/json" },
|
|
96
|
+
},
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return new Response(JSON.stringify({ error: "API Route not found" }), {
|
|
102
|
+
status: 404,
|
|
103
|
+
headers: { "Content-Type": "application/json" },
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export const GET = handleRequest;
|
|
108
|
+
export const POST = handleRequest;
|
|
109
|
+
export const PUT = handleRequest;
|
|
110
|
+
export const PATCH = handleRequest;
|
|
111
|
+
export const DELETE = handleRequest;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useTranslation } from "@veap/framework/intl";
|
|
4
|
+
import { useEffect } from "react";
|
|
5
|
+
|
|
6
|
+
export default function ErrorPage({
|
|
7
|
+
error,
|
|
8
|
+
reset,
|
|
9
|
+
}: {
|
|
10
|
+
error: Error & { digest?: string };
|
|
11
|
+
reset: () => void;
|
|
12
|
+
}) {
|
|
13
|
+
const { t } = useTranslation();
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
console.error(error);
|
|
16
|
+
}, [error]);
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<div className="flex min-h-screen flex-col items-center justify-center p-4">
|
|
20
|
+
<div className="max-w-md text-center">
|
|
21
|
+
<h2 className="text-2xl font-bold mb-4">{t("Something went wrong!")}</h2>
|
|
22
|
+
<p className="text-muted-foreground mb-6">
|
|
23
|
+
{error.message || t("An unexpected error occurred.")}
|
|
24
|
+
</p>
|
|
25
|
+
<button
|
|
26
|
+
onClick={() => reset()}
|
|
27
|
+
className="px-4 py-2 bg-primary text-primary-foreground rounded-md hover:opacity-90"
|
|
28
|
+
>
|
|
29
|
+
{t("Try Again")}
|
|
30
|
+
</button>
|
|
31
|
+
</div>
|
|
32
|
+
</div>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
@import "tailwindcss";
|
|
2
|
+
@import "tw-animate-css";
|
|
3
|
+
|
|
4
|
+
@custom-variant dark (&:is(.dark *));
|
|
5
|
+
|
|
6
|
+
@theme inline {
|
|
7
|
+
--color-background: var(--background);
|
|
8
|
+
--color-foreground: var(--foreground);
|
|
9
|
+
--color-card: var(--card);
|
|
10
|
+
--color-card-foreground: var(--card-foreground);
|
|
11
|
+
--color-popover: var(--popover);
|
|
12
|
+
--color-popover-foreground: var(--popover-foreground);
|
|
13
|
+
--color-primary: var(--primary);
|
|
14
|
+
--color-primary-foreground: var(--primary-foreground);
|
|
15
|
+
--color-secondary: var(--secondary);
|
|
16
|
+
--color-secondary-foreground: var(--secondary-foreground);
|
|
17
|
+
--color-muted: var(--muted);
|
|
18
|
+
--color-muted-foreground: var(--muted-foreground);
|
|
19
|
+
--color-accent: var(--accent);
|
|
20
|
+
--color-accent-foreground: var(--accent-foreground);
|
|
21
|
+
--color-destructive: var(--destructive);
|
|
22
|
+
--color-destructive-foreground: var(--destructive-foreground);
|
|
23
|
+
--color-border: var(--border);
|
|
24
|
+
--color-input: var(--input);
|
|
25
|
+
--color-ring: var(--ring);
|
|
26
|
+
--color-chart-1: var(--chart-1);
|
|
27
|
+
--color-chart-2: var(--chart-2);
|
|
28
|
+
--color-chart-3: var(--chart-3);
|
|
29
|
+
--color-chart-4: var(--chart-4);
|
|
30
|
+
--color-chart-5: var(--chart-5);
|
|
31
|
+
--radius-sm: calc(var(--radius) * 0.6);
|
|
32
|
+
--radius-md: calc(var(--radius) * 0.8);
|
|
33
|
+
--radius-lg: var(--radius);
|
|
34
|
+
--radius-xl: calc(var(--radius) * 1.4);
|
|
35
|
+
--radius-2xl: calc(var(--radius) * 1.8);
|
|
36
|
+
--radius-3xl: calc(var(--radius) * 2.2);
|
|
37
|
+
--radius-4xl: calc(var(--radius) * 2.6);
|
|
38
|
+
--color-sidebar: var(--sidebar);
|
|
39
|
+
--color-sidebar-foreground: var(--sidebar-foreground);
|
|
40
|
+
--color-sidebar-primary: var(--sidebar-primary);
|
|
41
|
+
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
|
42
|
+
--color-sidebar-accent: var(--sidebar-accent);
|
|
43
|
+
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
|
44
|
+
--color-sidebar-border: var(--sidebar-border);
|
|
45
|
+
--color-sidebar-ring: var(--sidebar-ring);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
:root {
|
|
49
|
+
--radius: 0.625rem;
|
|
50
|
+
--background: oklch(1 0 0);
|
|
51
|
+
--foreground: oklch(0.145 0 0);
|
|
52
|
+
--card: oklch(1 0 0);
|
|
53
|
+
--card-foreground: oklch(0.145 0 0);
|
|
54
|
+
--popover: oklch(1 0 0);
|
|
55
|
+
--popover-foreground: oklch(0.145 0 0);
|
|
56
|
+
--primary: oklch(0.205 0 0);
|
|
57
|
+
--primary-foreground: oklch(0.985 0 0);
|
|
58
|
+
--secondary: oklch(0.97 0 0);
|
|
59
|
+
--secondary-foreground: oklch(0.205 0 0);
|
|
60
|
+
--muted: oklch(0.97 0 0);
|
|
61
|
+
--muted-foreground: oklch(0.556 0 0);
|
|
62
|
+
--accent: oklch(0.97 0 0);
|
|
63
|
+
--accent-foreground: oklch(0.205 0 0);
|
|
64
|
+
--destructive: oklch(0.577 0.245 27.325);
|
|
65
|
+
--border: oklch(0.922 0 0);
|
|
66
|
+
--input: oklch(0.922 0 0);
|
|
67
|
+
--ring: oklch(0.708 0 0);
|
|
68
|
+
--chart-1: oklch(0.646 0.222 41.116);
|
|
69
|
+
--chart-2: oklch(0.6 0.118 184.704);
|
|
70
|
+
--chart-3: oklch(0.398 0.07 227.392);
|
|
71
|
+
--chart-4: oklch(0.828 0.189 84.429);
|
|
72
|
+
--chart-5: oklch(0.769 0.188 70.08);
|
|
73
|
+
--sidebar: oklch(0.985 0 0);
|
|
74
|
+
--sidebar-foreground: oklch(0.145 0 0);
|
|
75
|
+
--sidebar-primary: oklch(0.205 0 0);
|
|
76
|
+
--sidebar-primary-foreground: oklch(0.985 0 0);
|
|
77
|
+
--sidebar-accent: oklch(0.97 0 0);
|
|
78
|
+
--sidebar-accent-foreground: oklch(0.205 0 0);
|
|
79
|
+
--sidebar-border: oklch(0.922 0 0);
|
|
80
|
+
--sidebar-ring: oklch(0.708 0 0);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.dark {
|
|
84
|
+
--background: oklch(0.145 0 0);
|
|
85
|
+
--foreground: oklch(0.985 0 0);
|
|
86
|
+
--card: oklch(0.205 0 0);
|
|
87
|
+
--card-foreground: oklch(0.985 0 0);
|
|
88
|
+
--popover: oklch(0.205 0 0);
|
|
89
|
+
--popover-foreground: oklch(0.985 0 0);
|
|
90
|
+
--primary: oklch(0.922 0 0);
|
|
91
|
+
--primary-foreground: oklch(0.205 0 0);
|
|
92
|
+
--secondary: oklch(0.269 0 0);
|
|
93
|
+
--secondary-foreground: oklch(0.985 0 0);
|
|
94
|
+
--muted: oklch(0.269 0 0);
|
|
95
|
+
--muted-foreground: oklch(0.708 0 0);
|
|
96
|
+
--accent: oklch(0.269 0 0);
|
|
97
|
+
--accent-foreground: oklch(0.985 0 0);
|
|
98
|
+
--destructive: oklch(0.704 0.191 22.216);
|
|
99
|
+
--border: oklch(1 0 0 / 10%);
|
|
100
|
+
--input: oklch(1 0 0 / 15%);
|
|
101
|
+
--ring: oklch(0.556 0 0);
|
|
102
|
+
--chart-1: oklch(0.488 0.243 264.376);
|
|
103
|
+
--chart-2: oklch(0.696 0.17 162.48);
|
|
104
|
+
--chart-3: oklch(0.769 0.188 70.08);
|
|
105
|
+
--chart-4: oklch(0.627 0.265 303.9);
|
|
106
|
+
--chart-5: oklch(0.645 0.246 16.439);
|
|
107
|
+
--sidebar: oklch(0.205 0 0);
|
|
108
|
+
--sidebar-foreground: oklch(0.985 0 0);
|
|
109
|
+
--sidebar-primary: oklch(0.488 0.243 264.376);
|
|
110
|
+
--sidebar-primary-foreground: oklch(0.985 0 0);
|
|
111
|
+
--sidebar-accent: oklch(0.269 0 0);
|
|
112
|
+
--sidebar-accent-foreground: oklch(0.985 0 0);
|
|
113
|
+
--sidebar-border: oklch(1 0 0 / 10%);
|
|
114
|
+
--sidebar-ring: oklch(0.556 0 0);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
@layer base {
|
|
118
|
+
* {
|
|
119
|
+
@apply border-border outline-ring/50;
|
|
120
|
+
}
|
|
121
|
+
body {
|
|
122
|
+
@apply bg-background text-foreground;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import type { Metadata } from "next";
|
|
2
|
+
import { Geist, Geist_Mono } from "next/font/google";
|
|
3
|
+
import "./globals.css";
|
|
4
|
+
import { I18nProvider } from "@veap/framework/intl/server";
|
|
5
|
+
import { ExtensionPoint, getPathPrefix } from "@veap/framework/plugins/server";
|
|
6
|
+
import { AppProvider } from "@veap/framework/react";
|
|
7
|
+
import { cookies } from "next/headers";
|
|
8
|
+
import { initializeSystem } from "@/lib/veap";
|
|
9
|
+
import {
|
|
10
|
+
getCurrentSession,
|
|
11
|
+
isSystemInstalled,
|
|
12
|
+
} from "@veap/framework/auth/server";
|
|
13
|
+
|
|
14
|
+
const geistSans = Geist({
|
|
15
|
+
variable: "--font-geist-sans",
|
|
16
|
+
subsets: ["latin"],
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
const geistMono = Geist_Mono({
|
|
20
|
+
variable: "--font-geist-mono",
|
|
21
|
+
subsets: ["latin"],
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
export const metadata: Metadata = {
|
|
25
|
+
title: "Veap App",
|
|
26
|
+
description: "Built with Veap framework",
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
// The whole layout is request-time work: it boots Veap (providers + plugin
|
|
30
|
+
// context), reads cookies and renders plugin extension points. Without this,
|
|
31
|
+
// Next.js prerenders /_not-found during `next build` while
|
|
32
|
+
// NEXT_PHASE=phase-production-build - in which `Application.bootstrap()`
|
|
33
|
+
// intentionally skips provider boot - and context-bound helpers (e.g.
|
|
34
|
+
// getPathPrefix()) throw "Context is not bound".
|
|
35
|
+
export const dynamic = "force-dynamic";
|
|
36
|
+
|
|
37
|
+
export default async function RootLayout({
|
|
38
|
+
children,
|
|
39
|
+
}: Readonly<{
|
|
40
|
+
children: React.ReactNode;
|
|
41
|
+
}>) {
|
|
42
|
+
await initializeSystem();
|
|
43
|
+
const installed = await isSystemInstalled();
|
|
44
|
+
|
|
45
|
+
const session = installed
|
|
46
|
+
? await getCurrentSession()
|
|
47
|
+
: { session: null, user: null };
|
|
48
|
+
|
|
49
|
+
const pathPrefix = await getPathPrefix();
|
|
50
|
+
|
|
51
|
+
const cookieStore = await cookies();
|
|
52
|
+
const locale = cookieStore.get("VEAP_LOCALE")?.value || "en";
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
<html lang={locale} suppressHydrationWarning>
|
|
56
|
+
<body
|
|
57
|
+
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
|
|
58
|
+
>
|
|
59
|
+
<I18nProvider>
|
|
60
|
+
<AppProvider initialSession={session} prefix={pathPrefix}>
|
|
61
|
+
<div className="min-h-screen relative bg-background">
|
|
62
|
+
<ExtensionPoint target="app" point="before-content" />
|
|
63
|
+
{children}
|
|
64
|
+
<ExtensionPoint target="app" point="after-content" />
|
|
65
|
+
</div>
|
|
66
|
+
</AppProvider>
|
|
67
|
+
</I18nProvider>
|
|
68
|
+
</body>
|
|
69
|
+
</html>
|
|
70
|
+
);
|
|
71
|
+
}
|