@next-friday/create-app 2.0.0 → 2.1.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 +16 -15
- package/dist/main.mjs +18 -7
- package/package.json +2 -2
- package/templates/base/pnpm-workspace.yaml +24 -2
- package/templates/payload/README.md +1 -1
- package/templates/payload/package.json +0 -1
- package/templates/payload/src/payload.config.test.ts +31 -0
- package/templates/turborepo/.agents/rules/typescript-first.md +7 -0
- package/templates/turborepo/.agents/rules.json +41 -0
- package/templates/turborepo/.claude/rules/deterministic-testing.md +21 -0
- package/templates/turborepo/.claude/rules/typescript-first.md +15 -0
- package/templates/turborepo/.cursor/rules/deterministic-testing.mdc +21 -0
- package/templates/turborepo/.cursor/rules/typescript-first.mdc +13 -0
- package/templates/turborepo/.dockerignore +12 -0
- package/templates/turborepo/.editorconfig-checker.json +3 -0
- package/templates/turborepo/.env.example +4 -0
- package/templates/turborepo/.github/ISSUE_TEMPLATE/bug.yml +66 -0
- package/templates/turborepo/.github/ISSUE_TEMPLATE/feature.yml +51 -0
- package/templates/turborepo/.github/dependabot.yml +79 -0
- package/templates/turborepo/.github/labeler.yml +25 -0
- package/templates/turborepo/.github/pull_request_template.md +25 -0
- package/templates/turborepo/.github/scripts/affected-apps.sh +31 -0
- package/templates/turborepo/.github/workflows/ci.yml +300 -0
- package/templates/turborepo/.github/workflows/deploy.yml +200 -0
- package/templates/turborepo/.github/workflows/sast.yml +46 -0
- package/templates/turborepo/.lintstagedrc.json +11 -0
- package/templates/turborepo/.prettierignore +5 -0
- package/templates/turborepo/AGENTS.md +20 -0
- package/templates/turborepo/CONTRIBUTING.md +58 -0
- package/templates/turborepo/README.md +114 -0
- package/templates/turborepo/_gitignore +53 -0
- package/templates/turborepo/apps/app/.dependency-cruiser.json +34 -0
- package/templates/turborepo/apps/app/Dockerfile +25 -0
- package/templates/turborepo/apps/app/app/favicon.ico +0 -0
- package/templates/turborepo/apps/app/app/globals.css +26 -0
- package/templates/turborepo/apps/app/app/health/route.test.ts +20 -0
- package/templates/turborepo/apps/app/app/health/route.ts +23 -0
- package/templates/turborepo/apps/app/app/layout.tsx +29 -0
- package/templates/turborepo/apps/app/app/page.test.ts +11 -0
- package/templates/turborepo/apps/app/app/page.tsx +8 -0
- package/templates/turborepo/apps/app/next.config.ts +40 -0
- package/templates/turborepo/apps/app/package.json +41 -0
- package/templates/turborepo/apps/app/playwright.config.ts +27 -0
- package/templates/turborepo/apps/app/postcss.config.mjs +7 -0
- package/templates/turborepo/apps/app/test/home.e2e.test.ts +11 -0
- package/templates/turborepo/apps/app/tsconfig.json +33 -0
- package/templates/turborepo/apps/app/vitest.config.ts +12 -0
- package/templates/turborepo/apps/backoffice/.dependency-cruiser.json +34 -0
- package/templates/turborepo/apps/backoffice/Dockerfile +28 -0
- package/templates/turborepo/apps/backoffice/next.config.ts +48 -0
- package/templates/turborepo/apps/backoffice/package.json +49 -0
- package/templates/turborepo/apps/backoffice/playwright.config.ts +27 -0
- package/templates/turborepo/apps/backoffice/src/app/(frontend)/layout.tsx +18 -0
- package/templates/turborepo/apps/backoffice/src/app/(frontend)/page.test.ts +14 -0
- package/templates/turborepo/apps/backoffice/src/app/(frontend)/page.tsx +14 -0
- package/templates/turborepo/apps/backoffice/src/app/(frontend)/styles.css +15 -0
- package/templates/turborepo/apps/backoffice/src/app/(payload)/admin/[[...segments]]/not-found.tsx +24 -0
- package/templates/turborepo/apps/backoffice/src/app/(payload)/admin/[[...segments]]/page.tsx +24 -0
- package/templates/turborepo/apps/backoffice/src/app/(payload)/admin/importMap.ts +6 -0
- package/templates/turborepo/apps/backoffice/src/app/(payload)/api/[...slug]/route.ts +19 -0
- package/templates/turborepo/apps/backoffice/src/app/(payload)/custom.scss +0 -0
- package/templates/turborepo/apps/backoffice/src/app/(payload)/layout.tsx +31 -0
- package/templates/turborepo/apps/backoffice/src/app/health/route.test.ts +20 -0
- package/templates/turborepo/apps/backoffice/src/app/health/route.ts +23 -0
- package/templates/turborepo/apps/backoffice/src/collections/media.test.ts +27 -0
- package/templates/turborepo/apps/backoffice/src/collections/media.ts +18 -0
- package/templates/turborepo/apps/backoffice/src/collections/users.test.ts +10 -0
- package/templates/turborepo/apps/backoffice/src/collections/users.ts +10 -0
- package/templates/turborepo/apps/backoffice/src/environment.test.ts +32 -0
- package/templates/turborepo/apps/backoffice/src/environment.ts +27 -0
- package/templates/turborepo/apps/backoffice/src/migrations/20261030_005055_initial.json +969 -0
- package/templates/turborepo/apps/backoffice/src/migrations/20261030_005055_initial.ts +134 -0
- package/templates/turborepo/apps/backoffice/src/migrations/index.ts +9 -0
- package/templates/turborepo/apps/backoffice/src/payload-types.ts +339 -0
- package/templates/turborepo/apps/backoffice/src/payload.config.test.ts +31 -0
- package/templates/turborepo/apps/backoffice/src/payload.config.ts +50 -0
- package/templates/turborepo/apps/backoffice/test/home.e2e.test.ts +16 -0
- package/templates/turborepo/apps/backoffice/tsconfig.json +34 -0
- package/templates/turborepo/apps/backoffice/vitest.config.ts +19 -0
- package/templates/turborepo/eslint.config.ts +40 -0
- package/templates/turborepo/knip.json +9 -0
- package/templates/turborepo/package.json +70 -0
- package/templates/turborepo/pnpm-workspace.yaml +46 -0
- package/templates/turborepo/prettier.config.ts +11 -0
- package/templates/turborepo/tsconfig.json +16 -0
- package/templates/turborepo/turbo.json +45 -0
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@next-friday-template-turborepo/backoffice",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"description": "Next Friday Payload backoffice.",
|
|
6
|
+
"license": "UNLICENSED",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"scripts": {
|
|
9
|
+
"build": "next build",
|
|
10
|
+
"depcruise": "depcruise --config .dependency-cruiser.json src test next.config.ts playwright.config.ts vitest.config.ts",
|
|
11
|
+
"dev": "next dev --port 3001",
|
|
12
|
+
"generate:importmap": "payload generate:importmap",
|
|
13
|
+
"generate:types": "payload generate:types",
|
|
14
|
+
"migrate": "payload migrate",
|
|
15
|
+
"migrate:create": "payload migrate:create",
|
|
16
|
+
"payload": "payload",
|
|
17
|
+
"start": "cp -R .next/static .next/standalone/apps/backoffice/.next/ && (test ! -d public || cp -R public .next/standalone/apps/backoffice/) && PORT=3001 node .next/standalone/apps/backoffice/server.js",
|
|
18
|
+
"test": "vitest run --passWithNoTests",
|
|
19
|
+
"test:changed": "if git rev-parse --quiet --verify origin/main > /dev/null && git diff --quiet \"$(git merge-base origin/main HEAD)\" -- pnpm-lock.yaml pnpm-workspace.yaml \"*package.json\" \"*tsconfig.json\" .husky; then vitest run --changed origin/main --passWithNoTests; else vitest run --passWithNoTests; fi",
|
|
20
|
+
"test:coverage": "vitest run --coverage --passWithNoTests",
|
|
21
|
+
"test:e2e": "playwright test",
|
|
22
|
+
"test:watch": "vitest",
|
|
23
|
+
"typecheck": "next typegen && tsc --noEmit -p tsconfig.json"
|
|
24
|
+
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"@payloadcms/db-postgres": "3.92.1",
|
|
27
|
+
"@payloadcms/next": "3.92.1",
|
|
28
|
+
"@payloadcms/richtext-lexical": "3.92.1",
|
|
29
|
+
"next": "16.4.3",
|
|
30
|
+
"payload": "3.92.1",
|
|
31
|
+
"react": "19.3.1",
|
|
32
|
+
"react-dom": "19.3.1",
|
|
33
|
+
"sharp": "0.35.5"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@playwright/test": "1.64.1",
|
|
37
|
+
"@types/node": "26.6.5",
|
|
38
|
+
"@types/react": "19.3.0",
|
|
39
|
+
"@types/react-dom": "19.3.0",
|
|
40
|
+
"@vitest/coverage-v8": "5.0.5",
|
|
41
|
+
"dependency-cruiser": "18.7.0",
|
|
42
|
+
"fast-check": "4.10.2",
|
|
43
|
+
"typescript": "7.0.2",
|
|
44
|
+
"vitest": "5.0.5"
|
|
45
|
+
},
|
|
46
|
+
"engines": {
|
|
47
|
+
"node": ">=24.22.1 <25"
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import {defineConfig, devices} from "@playwright/test";
|
|
2
|
+
|
|
3
|
+
const isCi = Boolean(process.env.CI);
|
|
4
|
+
|
|
5
|
+
export default defineConfig({
|
|
6
|
+
forbidOnly: isCi,
|
|
7
|
+
testDir: "test",
|
|
8
|
+
projects: [
|
|
9
|
+
{
|
|
10
|
+
name: "chromium",
|
|
11
|
+
use: devices["Desktop Chrome"],
|
|
12
|
+
},
|
|
13
|
+
],
|
|
14
|
+
use: {
|
|
15
|
+
baseURL: "http://localhost:3001",
|
|
16
|
+
trace: "on-first-retry",
|
|
17
|
+
},
|
|
18
|
+
webServer: {
|
|
19
|
+
url: "http://localhost:3001",
|
|
20
|
+
command: isCi
|
|
21
|
+
? "cp -R .next/static .next/standalone/apps/backoffice/.next/ && (test ! -d public || cp -R public .next/standalone/apps/backoffice/) && PORT=3001 node .next/standalone/apps/backoffice/server.js"
|
|
22
|
+
: "next dev --port 3001",
|
|
23
|
+
reuseExistingServer: !isCi,
|
|
24
|
+
},
|
|
25
|
+
reporter: isCi ? "github" : "list",
|
|
26
|
+
retries: isCi ? 2 : 0,
|
|
27
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type {Metadata} from "next";
|
|
2
|
+
|
|
3
|
+
import "./styles.css";
|
|
4
|
+
|
|
5
|
+
export const metadata: Metadata = {
|
|
6
|
+
title: "Next Friday Payload template",
|
|
7
|
+
description: "Starter application for Next Friday Payload projects.",
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export default function RootLayout(props: LayoutProps<"/">) {
|
|
11
|
+
const {children} = props;
|
|
12
|
+
|
|
13
|
+
return (
|
|
14
|
+
<html lang="en">
|
|
15
|
+
<body>{children}</body>
|
|
16
|
+
</html>
|
|
17
|
+
);
|
|
18
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import {createElement} from "react";
|
|
2
|
+
import {describe, expect, it} from "vitest";
|
|
3
|
+
import {renderToStaticMarkup} from "react-dom/server";
|
|
4
|
+
|
|
5
|
+
import Page from "./page";
|
|
6
|
+
|
|
7
|
+
describe("Page", () => {
|
|
8
|
+
it("renders the template heading and the admin link", () => {
|
|
9
|
+
const html = renderToStaticMarkup(createElement(Page));
|
|
10
|
+
|
|
11
|
+
expect(html).toContain("Next Friday Payload template");
|
|
12
|
+
expect(html).toContain('href="/admin"');
|
|
13
|
+
});
|
|
14
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import Link from "next/link";
|
|
2
|
+
|
|
3
|
+
export default function Page() {
|
|
4
|
+
return (
|
|
5
|
+
<main>
|
|
6
|
+
<h1>Next Friday Payload template</h1>
|
|
7
|
+
|
|
8
|
+
<p>
|
|
9
|
+
Edit <code>src/app/(frontend)/page.tsx</code> to get started, or open the{" "}
|
|
10
|
+
<Link href="/admin">admin panel</Link>.
|
|
11
|
+
</p>
|
|
12
|
+
</main>
|
|
13
|
+
);
|
|
14
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
font-family: system-ui, sans-serif;
|
|
3
|
+
color-scheme: light dark;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
main {
|
|
7
|
+
display: flex;
|
|
8
|
+
flex-direction: column;
|
|
9
|
+
gap: 1rem;
|
|
10
|
+
align-items: center;
|
|
11
|
+
justify-content: center;
|
|
12
|
+
min-height: 100vh;
|
|
13
|
+
padding: 4rem;
|
|
14
|
+
text-align: center;
|
|
15
|
+
}
|
package/templates/turborepo/apps/backoffice/src/app/(payload)/admin/[[...segments]]/not-found.tsx
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
|
|
2
|
+
/* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */
|
|
3
|
+
import type { Metadata } from 'next'
|
|
4
|
+
|
|
5
|
+
import config from '@payload-config'
|
|
6
|
+
import { NotFoundPage, generatePageMetadata } from '@payloadcms/next/views'
|
|
7
|
+
import { importMap } from '../importMap'
|
|
8
|
+
|
|
9
|
+
type Args = {
|
|
10
|
+
params: Promise<{
|
|
11
|
+
segments: string[]
|
|
12
|
+
}>
|
|
13
|
+
searchParams: Promise<{
|
|
14
|
+
[key: string]: string | string[]
|
|
15
|
+
}>
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const generateMetadata = ({ params, searchParams }: Args): Promise<Metadata> =>
|
|
19
|
+
generatePageMetadata({ config, params, searchParams })
|
|
20
|
+
|
|
21
|
+
const NotFound = ({ params, searchParams }: Args) =>
|
|
22
|
+
NotFoundPage({ config, params, searchParams, importMap })
|
|
23
|
+
|
|
24
|
+
export default NotFound
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
|
|
2
|
+
/* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */
|
|
3
|
+
import type { Metadata } from 'next'
|
|
4
|
+
|
|
5
|
+
import config from '@payload-config'
|
|
6
|
+
import { RootPage, generatePageMetadata } from '@payloadcms/next/views'
|
|
7
|
+
import { importMap } from '../importMap'
|
|
8
|
+
|
|
9
|
+
type Args = {
|
|
10
|
+
params: Promise<{
|
|
11
|
+
segments: string[]
|
|
12
|
+
}>
|
|
13
|
+
searchParams: Promise<{
|
|
14
|
+
[key: string]: string | string[]
|
|
15
|
+
}>
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const generateMetadata = ({ params, searchParams }: Args): Promise<Metadata> =>
|
|
19
|
+
generatePageMetadata({ config, params, searchParams })
|
|
20
|
+
|
|
21
|
+
const Page = ({ params, searchParams }: Args) =>
|
|
22
|
+
RootPage({ config, params, searchParams, importMap })
|
|
23
|
+
|
|
24
|
+
export default Page
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { CollectionCards as CollectionCards_f9c02e79a4aed9a3924487c0cd4cafb1 } from '@payloadcms/next/rsc'
|
|
2
|
+
|
|
3
|
+
/** @type import('payload').ImportMap */
|
|
4
|
+
export const importMap = {
|
|
5
|
+
"@payloadcms/next/rsc#CollectionCards": CollectionCards_f9c02e79a4aed9a3924487c0cd4cafb1
|
|
6
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
|
|
2
|
+
/* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */
|
|
3
|
+
import config from '@payload-config'
|
|
4
|
+
import '@payloadcms/next/css'
|
|
5
|
+
import {
|
|
6
|
+
REST_DELETE,
|
|
7
|
+
REST_GET,
|
|
8
|
+
REST_OPTIONS,
|
|
9
|
+
REST_PATCH,
|
|
10
|
+
REST_POST,
|
|
11
|
+
REST_PUT,
|
|
12
|
+
} from '@payloadcms/next/routes'
|
|
13
|
+
|
|
14
|
+
export const GET = REST_GET(config)
|
|
15
|
+
export const POST = REST_POST(config)
|
|
16
|
+
export const DELETE = REST_DELETE(config)
|
|
17
|
+
export const PATCH = REST_PATCH(config)
|
|
18
|
+
export const PUT = REST_PUT(config)
|
|
19
|
+
export const OPTIONS = REST_OPTIONS(config)
|
|
File without changes
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
|
|
2
|
+
/* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */
|
|
3
|
+
import config from '@payload-config'
|
|
4
|
+
import '@payloadcms/next/css'
|
|
5
|
+
import type { ServerFunctionClient } from 'payload'
|
|
6
|
+
import { handleServerFunctions, RootLayout } from '@payloadcms/next/layouts'
|
|
7
|
+
import React from 'react'
|
|
8
|
+
|
|
9
|
+
import { importMap } from './admin/importMap'
|
|
10
|
+
import './custom.scss'
|
|
11
|
+
|
|
12
|
+
type Args = {
|
|
13
|
+
children: React.ReactNode
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const serverFunction: ServerFunctionClient = async function (args) {
|
|
17
|
+
'use server'
|
|
18
|
+
return handleServerFunctions({
|
|
19
|
+
...args,
|
|
20
|
+
config,
|
|
21
|
+
importMap,
|
|
22
|
+
})
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const Layout = ({ children }: Args) => (
|
|
26
|
+
<RootLayout config={config} importMap={importMap} serverFunction={serverFunction}>
|
|
27
|
+
{children}
|
|
28
|
+
</RootLayout>
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
export default Layout
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import {describe, expect, it, vi} from "vitest";
|
|
2
|
+
|
|
3
|
+
import {GET} from "./route";
|
|
4
|
+
|
|
5
|
+
vi.mock("next/server", () => ({
|
|
6
|
+
connection: vi.fn(async () => {}),
|
|
7
|
+
}));
|
|
8
|
+
|
|
9
|
+
describe("GET /health", () => {
|
|
10
|
+
it("reports ok without caching", async () => {
|
|
11
|
+
const response = await GET();
|
|
12
|
+
|
|
13
|
+
expect(response.status).toBe(200);
|
|
14
|
+
expect(response.headers.get("Cache-Control")).toBe("no-store");
|
|
15
|
+
|
|
16
|
+
await expect(response.json()).resolves.toEqual({
|
|
17
|
+
status: "ok",
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import {connection} from "next/server";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Report that the server is up, for platform health checks. Rendered per request and never cached, so a
|
|
5
|
+
* stopped server cannot pass from a cached response.
|
|
6
|
+
* @returns A JSON response with `status: "ok"`.
|
|
7
|
+
*/
|
|
8
|
+
async function get(): Promise<Response> {
|
|
9
|
+
await connection();
|
|
10
|
+
|
|
11
|
+
return Response.json(
|
|
12
|
+
{
|
|
13
|
+
status: "ok",
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
headers: {
|
|
17
|
+
"Cache-Control": "no-store",
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export {get as GET};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import {describe, expect, it} from "vitest";
|
|
2
|
+
import type {PayloadRequest} from "payload";
|
|
3
|
+
|
|
4
|
+
import {Media} from "./media";
|
|
5
|
+
|
|
6
|
+
describe("Media", () => {
|
|
7
|
+
it("lets anyone read uploaded files", () => {
|
|
8
|
+
const read = Media.access?.read;
|
|
9
|
+
|
|
10
|
+
expect(read).toBeTypeOf("function");
|
|
11
|
+
|
|
12
|
+
expect(
|
|
13
|
+
read?.({
|
|
14
|
+
req: {} as PayloadRequest,
|
|
15
|
+
}),
|
|
16
|
+
).toBe(true);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it("requires alt text", () => {
|
|
20
|
+
expect(Media.fields).toContainEqual(
|
|
21
|
+
expect.objectContaining({
|
|
22
|
+
name: "alt",
|
|
23
|
+
required: true,
|
|
24
|
+
}),
|
|
25
|
+
);
|
|
26
|
+
});
|
|
27
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type {CollectionConfig} from "payload";
|
|
2
|
+
|
|
3
|
+
export const Media: CollectionConfig = {
|
|
4
|
+
slug: "media",
|
|
5
|
+
fields: [
|
|
6
|
+
{
|
|
7
|
+
name: "alt",
|
|
8
|
+
type: "text",
|
|
9
|
+
required: true,
|
|
10
|
+
},
|
|
11
|
+
],
|
|
12
|
+
upload: {
|
|
13
|
+
mimeTypes: ["image/*"],
|
|
14
|
+
},
|
|
15
|
+
access: {
|
|
16
|
+
read: () => true,
|
|
17
|
+
},
|
|
18
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import {describe, expect, it} from "vitest";
|
|
2
|
+
|
|
3
|
+
import {Users} from "./users";
|
|
4
|
+
|
|
5
|
+
describe("Users", () => {
|
|
6
|
+
it("enables authentication and titles users by email", () => {
|
|
7
|
+
expect(Users.auth).toBe(true);
|
|
8
|
+
expect(Users.admin?.useAsTitle).toBe("email");
|
|
9
|
+
});
|
|
10
|
+
});
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import {afterEach, describe, expect, it, vi} from "vitest";
|
|
2
|
+
import {assert, property, string} from "fast-check";
|
|
3
|
+
|
|
4
|
+
import {requireEnvironment} from "./environment";
|
|
5
|
+
|
|
6
|
+
afterEach(() => {
|
|
7
|
+
vi.unstubAllEnvs();
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
describe("requireEnvironment", () => {
|
|
11
|
+
it("returns any non-empty value unchanged", () => {
|
|
12
|
+
assert(
|
|
13
|
+
property(
|
|
14
|
+
string({
|
|
15
|
+
minLength: 1,
|
|
16
|
+
}).filter(value => !value.includes("\0")),
|
|
17
|
+
value => {
|
|
18
|
+
vi.stubEnv("EXAMPLE_VALUE", value);
|
|
19
|
+
expect(requireEnvironment("EXAMPLE_VALUE")).toBe(value);
|
|
20
|
+
},
|
|
21
|
+
),
|
|
22
|
+
);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it.each([undefined, ""])("throws when the value is %j", value => {
|
|
26
|
+
vi.stubEnv("EXAMPLE_VALUE", value);
|
|
27
|
+
|
|
28
|
+
expect(() => requireEnvironment("EXAMPLE_VALUE")).toThrow(
|
|
29
|
+
"EXAMPLE_VALUE environment variable is required",
|
|
30
|
+
);
|
|
31
|
+
});
|
|
32
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import {existsSync} from "node:fs";
|
|
2
|
+
import {fileURLToPath} from "node:url";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Read a required environment variable.
|
|
7
|
+
* @param name - Variable name.
|
|
8
|
+
* @returns The variable's value.
|
|
9
|
+
*/
|
|
10
|
+
export function requireEnvironment(name: string): string {
|
|
11
|
+
const environmentFile = path.resolve(
|
|
12
|
+
path.dirname(fileURLToPath(import.meta.url)),
|
|
13
|
+
"../../../.env",
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
if (existsSync(environmentFile)) {
|
|
17
|
+
process.loadEnvFile(environmentFile);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const value = process.env[name];
|
|
21
|
+
|
|
22
|
+
if (value === undefined || value === "") {
|
|
23
|
+
throw new Error(`${name} environment variable is required`);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return value;
|
|
27
|
+
}
|