create-tigra 3.0.3 → 3.1.0
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 +8 -2
- package/bin/create-tigra.js +5 -19
- package/modules/email-verification/client/hooks/useVerification.ts +3 -3
- package/package.json +1 -1
- package/template/.agents/skills/security-audit/AI-AND-LLM.md +83 -0
- package/template/.agents/skills/security-audit/ATTACK-CLASSES.md +130 -0
- package/template/.agents/skills/security-audit/CLIENT-SIDE.md +83 -0
- package/template/.agents/skills/security-audit/CLOUD-AND-DEPLOYMENT.md +86 -0
- package/template/.agents/skills/security-audit/DATA-ISOLATION-AND-LIFECYCLE.md +84 -0
- package/template/.agents/skills/security-audit/DESKTOP-MOBILE-AND-LOCAL-IPC.md +89 -0
- package/template/.agents/skills/security-audit/HUNTING.md +251 -0
- package/template/.agents/skills/security-audit/LICENSE +21 -0
- package/template/.agents/skills/security-audit/MEMORY-SAFETY-AND-BINARY.md +101 -0
- package/template/.agents/skills/security-audit/PROTOCOLS-RPC-AND-MESSAGING.md +81 -0
- package/template/.agents/skills/security-audit/RECONNAISSANCE.md +156 -0
- package/template/.agents/skills/security-audit/RESOURCE-EXHAUSTION-AND-AVAILABILITY.md +78 -0
- package/template/.agents/skills/security-audit/SKILL.md +192 -0
- package/template/.agents/skills/security-audit/SOURCE.md +5 -0
- package/template/.agents/skills/security-audit/SUPPLY-CHAIN-AND-RELEASE.md +73 -0
- package/template/.agents/skills/security-audit/VALIDATION-AND-REPORTING.md +186 -0
- package/template/.agents/skills/security-audit/WEB-PROTOCOL-AND-AUTH.md +105 -0
- package/template/.agents/skills/security-audit/report-schema.json +461 -0
- package/template/.agents/skills/security-audit/validate-coverage-ledger.cjs +872 -0
- package/template/.agents/skills/security-audit/validate-coverage-ledger.test.cjs +740 -0
- package/template/.agents/skills/security-audit/validate-findings.cjs +773 -0
- package/template/.agents/skills/security-audit/validate-findings.test.cjs +652 -0
- package/template/AGENTS.md +45 -0
- package/template/client/AGENTS.md +22 -0
- package/template/client/Dockerfile +11 -1
- package/template/client/package-lock.json +410 -324
- package/template/client/package.json +4 -4
- package/template/client/scripts/next-dev.cjs +47 -0
- package/template/client/src/app/(auth)/layout.tsx +9 -0
- package/template/client/src/app/(auth)/loading.tsx +7 -0
- package/template/client/src/app/(main)/layout.tsx +11 -0
- package/template/client/src/app/(main)/loading.tsx +7 -0
- package/template/client/src/app/globals.css +4 -0
- package/template/client/src/app/loading.tsx +2 -6
- package/template/client/src/app/not-found.tsx +2 -3
- package/template/client/src/app/providers.tsx +6 -3
- package/template/client/src/components/common/AppLink.tsx +84 -0
- package/template/client/src/components/common/EmptyState.tsx +2 -2
- package/template/client/src/components/common/Pagination.tsx +3 -2
- package/template/client/src/components/common/RouteLoadingShell.tsx +21 -0
- package/template/client/src/components/common/SmoothNavigationProvider.tsx +151 -0
- package/template/client/src/components/layout/Header.tsx +12 -12
- package/template/client/src/features/admin/hooks/useAdminSessions.ts +2 -2
- package/template/client/src/features/admin/hooks/useAdminUsers.ts +3 -3
- package/template/client/src/features/auth/components/AuthInitializer.tsx +3 -2
- package/template/client/src/features/auth/components/LoginForm.tsx +3 -3
- package/template/client/src/features/auth/components/RegisterForm.tsx +3 -3
- package/template/client/src/features/auth/hooks/useAuth.ts +2 -2
- package/template/client/src/features/auth/hooks/usePasswordReset.ts +2 -2
- package/template/client/src/hooks/useAppRouter.ts +40 -0
- package/template/client/src/instrumentation-client.ts +29 -21
- package/template/client/src/instrumentation.ts +21 -17
- package/template/client/src/styles/themes/default.css +1 -1
- package/template/gitignore +0 -6
- package/template/server/AGENTS.md +28 -0
- package/template/server/Dockerfile +9 -3
- package/template/server/package-lock.json +671 -522
- package/template/server/package.json +8 -8
- package/template/server/src/modules/auth/__tests__/auth.service.test.ts +25 -41
- package/template/server/src/modules/auth/auth.repo.ts +0 -12
- package/template/server/src/modules/auth/auth.service.ts +29 -57
- package/template/_claude/QUICK_REFERENCE.md +0 -193
- package/template/_claude/README.md +0 -53
- package/template/_claude/commands/create-client.md +0 -878
- package/template/_claude/commands/create-server.md +0 -388
- package/template/_claude/hooks/restrict-paths.sh +0 -51
- package/template/_claude/rules/client/01-project-structure.md +0 -147
- package/template/_claude/rules/client/02-components-and-types.md +0 -146
- package/template/_claude/rules/client/03-data-and-state.md +0 -195
- package/template/_claude/rules/client/04-design-system.md +0 -408
- package/template/_claude/rules/client/05-security.md +0 -55
- package/template/_claude/rules/client/06-ux-checklist.md +0 -111
- package/template/_claude/rules/client/07-deployment.md +0 -99
- package/template/_claude/rules/client/08-lockfile-cross-platform.md +0 -79
- package/template/_claude/rules/client/core.md +0 -46
- package/template/_claude/rules/global/completion-reports.md +0 -178
- package/template/_claude/rules/global/core.md +0 -104
- package/template/_claude/rules/global/investigation-before-conclusions.md +0 -57
- package/template/_claude/rules/server/core.md +0 -52
- package/template/_claude/rules/server/database.md +0 -124
- package/template/_claude/rules/server/deployment.md +0 -78
- package/template/_claude/rules/server/project-conventions.md +0 -254
- package/template/_claude/rules/server/response-handling.md +0 -144
- package/template/_claude/settings.json +0 -15
- package/template/_claude/skills/clean-ui/SKILL.md +0 -63
- package/template/_claude/skills/role/SKILL.md +0 -39
- package/template/_claude/skills/theme/SKILL.md +0 -109
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
import type React from 'react';
|
|
4
4
|
import { Suspense } from 'react';
|
|
5
5
|
|
|
6
|
-
import Link from 'next/link';
|
|
7
6
|
import { useSearchParams } from 'next/navigation';
|
|
8
7
|
import { useForm } from 'react-hook-form';
|
|
9
8
|
import { zodResolver } from '@hookform/resolvers/zod';
|
|
@@ -13,6 +12,7 @@ import { Loader2 } from 'lucide-react';
|
|
|
13
12
|
import { Button } from '@/components/ui/button';
|
|
14
13
|
import { Input } from '@/components/ui/input';
|
|
15
14
|
import { Label } from '@/components/ui/label';
|
|
15
|
+
import { AppLink } from '@/components/common/AppLink';
|
|
16
16
|
import { useAuth } from '../hooks/useAuth';
|
|
17
17
|
import { ROUTES } from '@/lib/constants/routes';
|
|
18
18
|
import { cn } from '@/lib/utils';
|
|
@@ -100,12 +100,12 @@ const LoginFormInner = (): React.ReactElement => {
|
|
|
100
100
|
|
|
101
101
|
<p className="text-center text-sm text-muted-foreground">
|
|
102
102
|
Don't have an account?{' '}
|
|
103
|
-
<
|
|
103
|
+
<AppLink
|
|
104
104
|
href={ROUTES.REGISTER}
|
|
105
105
|
className="font-medium text-primary transition-colors duration-150 active:opacity-70 md:hover:text-primary/80"
|
|
106
106
|
>
|
|
107
107
|
Create account
|
|
108
|
-
</
|
|
108
|
+
</AppLink>
|
|
109
109
|
</p>
|
|
110
110
|
</div>
|
|
111
111
|
);
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
import type React from 'react';
|
|
4
4
|
|
|
5
|
-
import Link from 'next/link';
|
|
6
5
|
import { useForm } from 'react-hook-form';
|
|
7
6
|
import { zodResolver } from '@hookform/resolvers/zod';
|
|
8
7
|
import { z } from 'zod';
|
|
@@ -11,6 +10,7 @@ import { Loader2 } from 'lucide-react';
|
|
|
11
10
|
import { Button } from '@/components/ui/button';
|
|
12
11
|
import { Input } from '@/components/ui/input';
|
|
13
12
|
import { Label } from '@/components/ui/label';
|
|
13
|
+
import { AppLink } from '@/components/common/AppLink';
|
|
14
14
|
import { useAuth } from '../hooks/useAuth';
|
|
15
15
|
import { ROUTES } from '@/lib/constants/routes';
|
|
16
16
|
import { cn } from '@/lib/utils';
|
|
@@ -166,12 +166,12 @@ export const RegisterForm = (): React.ReactElement => {
|
|
|
166
166
|
|
|
167
167
|
<p className="text-center text-sm text-muted-foreground">
|
|
168
168
|
Already have an account?{' '}
|
|
169
|
-
<
|
|
169
|
+
<AppLink
|
|
170
170
|
href={ROUTES.LOGIN}
|
|
171
171
|
className="font-medium text-primary transition-colors duration-150 active:opacity-70 md:hover:text-primary/80"
|
|
172
172
|
>
|
|
173
173
|
Sign in
|
|
174
|
-
</
|
|
174
|
+
</AppLink>
|
|
175
175
|
</p>
|
|
176
176
|
</div>
|
|
177
177
|
);
|
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
import { useCallback, useRef } from 'react';
|
|
4
4
|
|
|
5
5
|
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
|
6
|
-
import { useRouter } from 'next/navigation';
|
|
7
6
|
import { toast } from 'sonner';
|
|
8
7
|
|
|
9
8
|
import { useAppDispatch, useAppSelector } from '@/store/hooks';
|
|
9
|
+
import { useAppRouter } from '@/hooks/useAppRouter';
|
|
10
10
|
import { getErrorMessage, isErrorCode, ERROR_CODES } from '@/lib/utils/error';
|
|
11
11
|
import { ROUTES } from '@/lib/constants/routes';
|
|
12
12
|
import { authService } from '../services/auth.service';
|
|
@@ -28,7 +28,7 @@ interface UseAuthReturn {
|
|
|
28
28
|
|
|
29
29
|
export const useAuth = (): UseAuthReturn => {
|
|
30
30
|
const dispatch = useAppDispatch();
|
|
31
|
-
const router =
|
|
31
|
+
const router = useAppRouter();
|
|
32
32
|
const queryClient = useQueryClient();
|
|
33
33
|
const { user, isAuthenticated, isInitializing, isLoggingOut } = useAppSelector((state) => state.auth);
|
|
34
34
|
const pendingRedirectRef = useRef<string>(ROUTES.DASHBOARD);
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { useMutation } from '@tanstack/react-query';
|
|
4
|
-
import { useRouter } from 'next/navigation';
|
|
5
4
|
import { toast } from 'sonner';
|
|
6
5
|
|
|
6
|
+
import { useAppRouter } from '@/hooks/useAppRouter';
|
|
7
7
|
import { ROUTES } from '@/lib/constants/routes';
|
|
8
8
|
import { getErrorMessage } from '@/lib/utils/error';
|
|
9
9
|
import { authService } from '../services/auth.service';
|
|
@@ -36,7 +36,7 @@ interface UseResetPasswordReturn {
|
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
export const useResetPassword = (): UseResetPasswordReturn => {
|
|
39
|
-
const router =
|
|
39
|
+
const router = useAppRouter();
|
|
40
40
|
|
|
41
41
|
const mutation = useMutation({
|
|
42
42
|
mutationFn: (data: { token: string; newPassword: string }) =>
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useCallback, useMemo } from 'react';
|
|
4
|
+
|
|
5
|
+
import { useRouter } from 'next/navigation';
|
|
6
|
+
|
|
7
|
+
import { useSmoothNavigation } from '@/components/common/SmoothNavigationProvider';
|
|
8
|
+
|
|
9
|
+
type AppRouter = ReturnType<typeof useRouter>;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* App Router wrapper that keeps Next.js from performing its instant scroll.
|
|
13
|
+
* Pass `{ scroll: false }` explicitly when navigation should preserve position.
|
|
14
|
+
*/
|
|
15
|
+
export const useAppRouter = (): AppRouter => {
|
|
16
|
+
const router = useRouter();
|
|
17
|
+
const { startNavigation } = useSmoothNavigation();
|
|
18
|
+
|
|
19
|
+
const push = useCallback<AppRouter['push']>(
|
|
20
|
+
(href, options) => {
|
|
21
|
+
if (options?.scroll !== false) {
|
|
22
|
+
startNavigation(href);
|
|
23
|
+
}
|
|
24
|
+
router.push(href, { ...options, scroll: false });
|
|
25
|
+
},
|
|
26
|
+
[router, startNavigation]
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
const replace = useCallback<AppRouter['replace']>(
|
|
30
|
+
(href, options) => {
|
|
31
|
+
if (options?.scroll !== false) {
|
|
32
|
+
startNavigation(href);
|
|
33
|
+
}
|
|
34
|
+
router.replace(href, { ...options, scroll: false });
|
|
35
|
+
},
|
|
36
|
+
[router, startNavigation]
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
return useMemo(() => ({ ...router, push, replace }), [router, push, replace]);
|
|
40
|
+
};
|
|
@@ -1,30 +1,38 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Browser
|
|
2
|
+
* Browser instrumentation.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* no-op and ships nothing that phones home.
|
|
7
|
-
*
|
|
8
|
-
* `process.env.NEXT_PUBLIC_*` is inlined at build time by Next — reference it
|
|
9
|
-
* literally (not via a computed key) so the value is statically replaced.
|
|
10
|
-
*
|
|
11
|
-
* Like the server side, this stays runtime + env-gated: we do NOT wrap
|
|
12
|
-
* next.config.ts with `withSentryConfig` (that would couple `next build` to a
|
|
13
|
-
* source-map upload auth token). withSentryConfig is the opt-in upgrade.
|
|
4
|
+
* Keep local dev cold: do not import @sentry/nextjs unless production has a
|
|
5
|
+
* public DSN. This avoids compiling the Sentry SDK for every local dev server.
|
|
14
6
|
*/
|
|
15
|
-
|
|
7
|
+
type SentryNext = typeof import('@sentry/nextjs');
|
|
8
|
+
type RouterTransitionStart = SentryNext['captureRouterTransitionStart'];
|
|
16
9
|
|
|
17
10
|
const dsn = process.env.NEXT_PUBLIC_SENTRY_DSN;
|
|
11
|
+
const sentryEnabled = process.env.NODE_ENV === 'production' && Boolean(dsn);
|
|
12
|
+
|
|
13
|
+
let sentryPromise: Promise<SentryNext> | null = null;
|
|
14
|
+
|
|
15
|
+
function loadSentry(): Promise<SentryNext | null> {
|
|
16
|
+
if (!sentryEnabled || !dsn) return Promise.resolve(null);
|
|
18
17
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
18
|
+
sentryPromise ??= import('@sentry/nextjs').then((Sentry) => {
|
|
19
|
+
Sentry.init({
|
|
20
|
+
dsn,
|
|
21
|
+
tracesSampleRate: process.env.NEXT_PUBLIC_SENTRY_TRACES_SAMPLE_RATE
|
|
22
|
+
? Number(process.env.NEXT_PUBLIC_SENTRY_TRACES_SAMPLE_RATE)
|
|
23
|
+
: 0.1,
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
return Sentry;
|
|
25
27
|
});
|
|
28
|
+
|
|
29
|
+
return sentryPromise;
|
|
26
30
|
}
|
|
27
31
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
export const onRouterTransitionStart =
|
|
32
|
+
void loadSentry();
|
|
33
|
+
|
|
34
|
+
export const onRouterTransitionStart = ((...args: Parameters<RouterTransitionStart>) => {
|
|
35
|
+
void loadSentry().then((Sentry) => {
|
|
36
|
+
Sentry?.captureRouterTransitionStart(...args);
|
|
37
|
+
});
|
|
38
|
+
}) as RouterTransitionStart;
|
|
@@ -1,28 +1,30 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Server/Edge runtime instrumentation
|
|
2
|
+
* Server/Edge runtime instrumentation.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* with the browser), so with no DSN this is a clean no-op and `next build`
|
|
7
|
-
* succeeds with zero Sentry credentials.
|
|
8
|
-
*
|
|
9
|
-
* NOTE: we deliberately do NOT wrap next.config.ts with `withSentryConfig`.
|
|
10
|
-
* That adds build-time source-map upload which requires a SENTRY_AUTH_TOKEN and
|
|
11
|
-
* would couple `next build` to credentials. Source-map upload is the opt-in
|
|
12
|
-
* upgrade — add `withSentryConfig` + an auth token when you want it. This setup
|
|
13
|
-
* stays purely runtime + env-gated.
|
|
4
|
+
* Keep local dev cold: importing @sentry/nextjs at module top-level makes Next
|
|
5
|
+
* compile the Sentry SDK before the first page, even with no DSN.
|
|
14
6
|
*/
|
|
15
|
-
|
|
7
|
+
type SentryNext = typeof import('@sentry/nextjs');
|
|
8
|
+
type RequestErrorHandler = SentryNext['captureRequestError'];
|
|
16
9
|
|
|
17
10
|
const dsn = process.env.NEXT_PUBLIC_SENTRY_DSN ?? process.env.SENTRY_DSN;
|
|
11
|
+
const sentryEnabled = process.env.NODE_ENV === 'production' && Boolean(dsn);
|
|
18
12
|
|
|
19
13
|
const tracesSampleRate = process.env.NEXT_PUBLIC_SENTRY_TRACES_SAMPLE_RATE
|
|
20
14
|
? Number(process.env.NEXT_PUBLIC_SENTRY_TRACES_SAMPLE_RATE)
|
|
21
15
|
: 0.1;
|
|
22
16
|
|
|
17
|
+
let sentryPromise: Promise<SentryNext> | null = null;
|
|
18
|
+
|
|
19
|
+
function loadSentry(): Promise<SentryNext | null> {
|
|
20
|
+
if (!sentryEnabled) return Promise.resolve(null);
|
|
21
|
+
sentryPromise ??= import('@sentry/nextjs');
|
|
22
|
+
return sentryPromise;
|
|
23
|
+
}
|
|
24
|
+
|
|
23
25
|
export async function register(): Promise<void> {
|
|
24
|
-
|
|
25
|
-
if (!dsn) return;
|
|
26
|
+
const Sentry = await loadSentry();
|
|
27
|
+
if (!Sentry || !dsn) return;
|
|
26
28
|
|
|
27
29
|
if (process.env.NEXT_RUNTIME === 'nodejs' || process.env.NEXT_RUNTIME === 'edge') {
|
|
28
30
|
Sentry.init({
|
|
@@ -33,6 +35,8 @@ export async function register(): Promise<void> {
|
|
|
33
35
|
}
|
|
34
36
|
}
|
|
35
37
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
38
|
+
export const onRequestError = (async (...args: Parameters<RequestErrorHandler>) => {
|
|
39
|
+
const Sentry = await loadSentry();
|
|
40
|
+
if (!Sentry) return;
|
|
41
|
+
return Sentry.captureRequestError(...args);
|
|
42
|
+
}) as RequestErrorHandler;
|
package/template/gitignore
CHANGED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Server Codex instructions
|
|
2
|
+
|
|
3
|
+
## Architecture and naming
|
|
4
|
+
|
|
5
|
+
- Request flow is route → controller → service → repository → Prisma. Routes register HTTP/schema metadata; controllers translate HTTP input/output; services own business rules; repositories contain database queries only.
|
|
6
|
+
- Domain modules live in `src/modules/<domain>/` with `<domain>.routes.ts`, `.controller.ts`, `.service.ts`, `.repo.ts`, and `.schemas.ts`.
|
|
7
|
+
- All API routes use the `/api/v1` prefix. Use the shared `successResponse`, `paginatedResponse`, and typed `AppError` contracts rather than custom response/error shapes.
|
|
8
|
+
- Use the shared logger and outbound HTTP client. Never add ad-hoc `console.log`, raw `fetch`, or per-call HTTP clients that bypass redaction, timeouts, and error mapping.
|
|
9
|
+
|
|
10
|
+
## Sources of truth
|
|
11
|
+
|
|
12
|
+
- `prisma/schema.prisma` owns models, relations, indexes, and database naming. Change schema through reviewed Prisma migrations; production is forward-only with `prisma migrate deploy`.
|
|
13
|
+
- `src/config/env.ts` owns runtime configuration validation. Keep `.env.example` and `.env.example.production` synchronized, but keep real secrets out of the repository and image.
|
|
14
|
+
- Zod route schemas own external input validation. Services own authorization and business invariants; repositories must not decide access policy.
|
|
15
|
+
|
|
16
|
+
## Security and deployment
|
|
17
|
+
|
|
18
|
+
- Preserve httpOnly cookie auth, production CORS allowlists, state-changing request origin checks, rate limiting, safe client IP handling, generic 500 responses, and server-side-only diagnostic logging.
|
|
19
|
+
- Use Prisma parameterization and explicit safe projections; never expose password hashes, tokens, internal errors, or secret-bearing upstream payloads.
|
|
20
|
+
- Keep public uploads under the static public root. Private uploads stay outside it and are served only through authenticated, owner-scoped routes; preserve filename/path validation.
|
|
21
|
+
- The production image runs non-root, applies `prisma migrate deploy` before `node dist/server.js`, and probes unauthenticated `GET /api/v1/live`. Preserve writable ownership for runtime storage and add explicit Alpine packages for new native runtime dependencies.
|
|
22
|
+
|
|
23
|
+
## Mandatory security completion gate
|
|
24
|
+
|
|
25
|
+
- Follow the root `AGENTS.md` security workflow for every meaningful server change: use `security-audit` guidance during implementation and run `codex-security:security-diff-scan` on the resulting patch when the Codex Security plugin is available.
|
|
26
|
+
- Review the concrete trust boundary, attacker-controlled input, affected principal/resource, authorization decision, validation point, and observable impact. Missing best practices without a reachable boundary failure are hardening notes, not confirmed vulnerabilities.
|
|
27
|
+
- Add or update focused regression coverage for changed authorization, validation, origin/CORS, cookie/session, upload/path, outbound-request, and data-isolation behavior. Never rely on client checks as security evidence.
|
|
28
|
+
- If a required security check cannot run, state the exact blocker and safe follow-up instead of reporting the server change as fully verified.
|
|
@@ -1,7 +1,11 @@
|
|
|
1
|
+
# syntax=docker/dockerfile:1
|
|
1
2
|
# ===================================================================
|
|
2
3
|
# Multi-stage Dockerfile for Production Deployment
|
|
3
4
|
# Optimized for Coolify, Docker Compose, and Kubernetes
|
|
4
5
|
# ===================================================================
|
|
6
|
+
# BuildKit cache mounts (--mount=type=cache) keep the npm download cache warm
|
|
7
|
+
# ACROSS builds, so a lockfile-change rebuild re-installs without re-downloading.
|
|
8
|
+
# Requires the `# syntax` line above; auto-enabled on Docker 23+ / any BuildKit.
|
|
5
9
|
|
|
6
10
|
# ===================================================================
|
|
7
11
|
# Stage 1: Dependencies (cached layer)
|
|
@@ -15,7 +19,8 @@ WORKDIR /app
|
|
|
15
19
|
COPY package.json package-lock.json* pnpm-lock.yaml* yarn.lock* ./
|
|
16
20
|
|
|
17
21
|
# Install dependencies (use the lockfile that exists)
|
|
18
|
-
RUN
|
|
22
|
+
RUN --mount=type=cache,target=/root/.npm \
|
|
23
|
+
if [ -f pnpm-lock.yaml ]; then \
|
|
19
24
|
npm install -g pnpm && pnpm install --prod --frozen-lockfile; \
|
|
20
25
|
elif [ -f yarn.lock ]; then \
|
|
21
26
|
yarn install --production --frozen-lockfile; \
|
|
@@ -32,7 +37,7 @@ RUN if [ -f pnpm-lock.yaml ]; then \
|
|
|
32
37
|
# --no-save keeps package.json and the lockfile untouched. Because this stage
|
|
33
38
|
# runs on the same Alpine (musl) base as the runtime stage, the engine binaries
|
|
34
39
|
# downloaded here are the correct ones for production.
|
|
35
|
-
RUN npm install --no-save --no-audit --no-fund \
|
|
40
|
+
RUN --mount=type=cache,target=/root/.npm npm install --no-save --no-audit --no-fund \
|
|
36
41
|
"prisma@$(node -p "require('@prisma/client/package.json').version")"
|
|
37
42
|
|
|
38
43
|
# ===================================================================
|
|
@@ -50,7 +55,8 @@ WORKDIR /app
|
|
|
50
55
|
# Copy package files and install ALL dependencies (including dev)
|
|
51
56
|
COPY package.json package-lock.json* pnpm-lock.yaml* yarn.lock* ./
|
|
52
57
|
|
|
53
|
-
RUN
|
|
58
|
+
RUN --mount=type=cache,target=/root/.npm \
|
|
59
|
+
if [ -f pnpm-lock.yaml ]; then \
|
|
54
60
|
npm install -g pnpm && pnpm install --frozen-lockfile; \
|
|
55
61
|
elif [ -f yarn.lock ]; then \
|
|
56
62
|
yarn install --frozen-lockfile; \
|