create-nextly-app 0.0.2-alpha.7 → 0.0.2-alpha.71
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 +36 -9
- package/dist/cli.cjs +3047 -32460
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.d.cts +1 -1
- package/dist/cli.d.ts +1 -1
- package/dist/cli.mjs +3048 -3602
- package/dist/cli.mjs.map +1 -1
- package/dist/create-nextly-5wiGMVI6.mjs +52550 -0
- package/dist/create-nextly-5wiGMVI6.mjs.map +1 -0
- package/dist/create-nextly-BbKGDrEC.cjs +52628 -0
- package/dist/create-nextly-BbKGDrEC.cjs.map +1 -0
- package/dist/index.cjs +3 -24861
- package/dist/index.d.cts +40 -34
- package/dist/index.d.ts +40 -34
- package/dist/index.mjs +1 -6
- package/package.json +28 -19
- package/skills/nextly-authentication/SKILL.md +187 -0
- package/skills/nextly-collections/SKILL.md +215 -0
- package/skills/nextly-deploy/SKILL.md +248 -0
- package/skills/nextly-install/SKILL.md +121 -0
- package/templates/base/.env.example +21 -6
- package/templates/base/AGENTS.md.template +226 -0
- package/templates/base/CLAUDE.md.template +1 -0
- package/templates/base/gitignore +42 -0
- package/templates/base/next.config.ts +11 -17
- package/templates/base/src/app/admin/[[...params]]/page.tsx +3 -0
- package/templates/base/src/app/admin/api/media/[[...path]]/route.ts +26 -0
- package/templates/base/src/app/globals.css +15 -2
- package/templates/base/src/app/layout.tsx +32 -18
- package/templates/base/src/lib/site-url.ts +55 -0
- package/templates/base/src/types/generated/nextly-types.ts +6 -4
- package/templates/base/src/types/generated/plugin-admin-components.generated.ts +6 -0
- package/templates/base/src/types/generated/plugin-admin-imports.generated.ts +32 -0
- package/templates/base/tsconfig.json +8 -1
- package/templates/blank/src/{components → app/(frontend)/components}/ThemeToggle.tsx +55 -24
- package/templates/blank/src/app/(frontend)/page.tsx +2 -2
- package/templates/blank/src/app/globals.css +37 -1
- package/templates/blank/src/app/layout.tsx +28 -18
- package/templates/blank/src/instrumentation.ts +32 -0
- package/templates/plugin/AGENTS.md.template +125 -0
- package/templates/plugin/CLAUDE.md.template +1 -0
- package/templates/plugin/README.md +48 -0
- package/templates/plugin/dev/.env.example +11 -0
- package/templates/plugin/dev/next.config.ts +43 -0
- package/templates/plugin/dev/nextly.config.ts +17 -0
- package/templates/plugin/dev/src/app/admin/[[...params]]/page.tsx +24 -0
- package/templates/plugin/dev/src/app/admin/api/[[...params]]/route.ts +19 -0
- package/templates/plugin/dev/src/app/layout.tsx +13 -0
- package/templates/plugin/dev/src/instrumentation.ts +25 -0
- package/templates/plugin/dev/src/seed-dev-user.ts +51 -0
- package/templates/plugin/dev/src/types/generated/plugin-admin-components.generated.ts +8 -0
- package/templates/plugin/dev/src/types/generated/plugin-admin-imports.generated.ts +37 -0
- package/templates/plugin/dev/tsconfig.json +22 -0
- package/templates/plugin/eslint.config.mjs +19 -0
- package/templates/plugin/gitignore +13 -0
- package/templates/plugin/src/admin/SettingsPage.tsx +57 -0
- package/templates/plugin/src/admin/index.ts +24 -0
- package/templates/plugin/src/collections/example.ts +13 -0
- package/templates/plugin/src/index.ts +6 -0
- package/templates/plugin/src/plugin.test.ts +35 -0
- package/templates/plugin/src/plugin.ts +48 -0
- package/templates/plugin/src/types.ts +17 -0
- package/templates/plugin/template.json +14 -0
- package/templates/plugin/tsconfig.json +18 -0
- package/templates/plugin/tsdown.config.ts +30 -0
- package/templates/plugin/vitest.config.ts +27 -0
- package/dist/chunk-RPGFJSDP.mjs +0 -28835
- package/dist/chunk-RPGFJSDP.mjs.map +0 -1
- package/dist/index.cjs.map +0 -1
- package/dist/index.mjs.map +0 -1
- package/templates/blank/src/access/README.md +0 -22
- package/templates/blank/src/collections/README.md +0 -22
- package/templates/blank/src/lib/README.md +0 -15
- package/templates/blank/src/singles/README.md +0 -19
- /package/templates/blank/src/{components → app/(frontend)/components}/README.md +0 -0
package/dist/index.d.cts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Project type / template selection.
|
|
3
3
|
* "blog" is the first content template added for alpha.
|
|
4
|
+
* "plugin" scaffolds a publishable Nextly plugin package + embedded /dev playground (D44/D45).
|
|
4
5
|
*/
|
|
5
|
-
type ProjectType = "blank" | "blog";
|
|
6
|
+
type ProjectType = "blank" | "blog" | "plugin";
|
|
6
7
|
/**
|
|
7
8
|
* Schema approach selection for content templates.
|
|
8
9
|
* - code-first: Full schema definitions in nextly.config.ts (like Payload CMS)
|
|
@@ -18,37 +19,42 @@ type DatabaseType = "postgresql" | "mysql" | "sqlite";
|
|
|
18
19
|
* Options for createNextly function
|
|
19
20
|
*/
|
|
20
21
|
interface CreateNextlyOptions {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
22
|
+
/** Working directory (defaults to process.cwd()) */
|
|
23
|
+
cwd?: string;
|
|
24
|
+
/** Skip interactive prompts and use defaults */
|
|
25
|
+
defaults?: boolean;
|
|
26
|
+
/** Pre-selected project type / template name */
|
|
27
|
+
projectType?: ProjectType;
|
|
28
|
+
/** Pre-selected database type */
|
|
29
|
+
database?: DatabaseType;
|
|
30
|
+
/** Skip dependency installation (useful for local testing before packages are published) */
|
|
31
|
+
skipInstall?: boolean;
|
|
32
|
+
/** Use yalc for local package installation (for testing before npm publish) */
|
|
33
|
+
useYalc?: boolean;
|
|
34
|
+
/**
|
|
35
|
+
* Project name derived from the CLI's positional `[directory]` argument.
|
|
36
|
+
* Must be a bare directory name (no slashes). Callers pass the basename
|
|
37
|
+
* of the user-provided argument.
|
|
38
|
+
*/
|
|
39
|
+
projectNameFromArg?: string;
|
|
40
|
+
/**
|
|
41
|
+
* When true, the CLI was invoked with "." meaning "install in current directory".
|
|
42
|
+
* Different from projectNameFromArg which creates a subdirectory.
|
|
43
|
+
*/
|
|
44
|
+
installInCwd?: boolean;
|
|
45
|
+
/** Schema approach for content templates (code-first, visual) */
|
|
46
|
+
approach?: ProjectApproach;
|
|
47
|
+
/** Path to local templates directory (for development, bypasses GitHub download) */
|
|
48
|
+
localTemplatePath?: string;
|
|
49
|
+
/** Git branch to take a template from; unset, it comes from this CLI's release */
|
|
50
|
+
branch?: string;
|
|
51
|
+
/**
|
|
52
|
+
* Whether to add the Nextly skills for coding agents to the project.
|
|
53
|
+
* False skips them without asking; otherwise the developer is asked, and
|
|
54
|
+
* defaults mode adds them.
|
|
55
|
+
*/
|
|
56
|
+
skills?: boolean;
|
|
50
57
|
}
|
|
51
|
-
|
|
52
58
|
/**
|
|
53
59
|
* Main entry point for scaffolding Nextly in a Next.js project.
|
|
54
60
|
*
|
|
@@ -64,6 +70,6 @@ interface CreateNextlyOptions {
|
|
|
64
70
|
* 5. Database selection (sqlite, postgresql, mysql)
|
|
65
71
|
* 6. Database connection string (only for postgresql/mysql)
|
|
66
72
|
*/
|
|
67
|
-
declare function createNextly(options?: CreateNextlyOptions): Promise<void>;
|
|
68
|
-
|
|
69
|
-
|
|
73
|
+
export declare function createNextly(options?: CreateNextlyOptions): Promise<void>;
|
|
74
|
+
export type { CreateNextlyOptions };
|
|
75
|
+
//# sourceMappingURL=index.d.cts.map
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Project type / template selection.
|
|
3
3
|
* "blog" is the first content template added for alpha.
|
|
4
|
+
* "plugin" scaffolds a publishable Nextly plugin package + embedded /dev playground (D44/D45).
|
|
4
5
|
*/
|
|
5
|
-
type ProjectType = "blank" | "blog";
|
|
6
|
+
type ProjectType = "blank" | "blog" | "plugin";
|
|
6
7
|
/**
|
|
7
8
|
* Schema approach selection for content templates.
|
|
8
9
|
* - code-first: Full schema definitions in nextly.config.ts (like Payload CMS)
|
|
@@ -18,37 +19,42 @@ type DatabaseType = "postgresql" | "mysql" | "sqlite";
|
|
|
18
19
|
* Options for createNextly function
|
|
19
20
|
*/
|
|
20
21
|
interface CreateNextlyOptions {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
22
|
+
/** Working directory (defaults to process.cwd()) */
|
|
23
|
+
cwd?: string;
|
|
24
|
+
/** Skip interactive prompts and use defaults */
|
|
25
|
+
defaults?: boolean;
|
|
26
|
+
/** Pre-selected project type / template name */
|
|
27
|
+
projectType?: ProjectType;
|
|
28
|
+
/** Pre-selected database type */
|
|
29
|
+
database?: DatabaseType;
|
|
30
|
+
/** Skip dependency installation (useful for local testing before packages are published) */
|
|
31
|
+
skipInstall?: boolean;
|
|
32
|
+
/** Use yalc for local package installation (for testing before npm publish) */
|
|
33
|
+
useYalc?: boolean;
|
|
34
|
+
/**
|
|
35
|
+
* Project name derived from the CLI's positional `[directory]` argument.
|
|
36
|
+
* Must be a bare directory name (no slashes). Callers pass the basename
|
|
37
|
+
* of the user-provided argument.
|
|
38
|
+
*/
|
|
39
|
+
projectNameFromArg?: string;
|
|
40
|
+
/**
|
|
41
|
+
* When true, the CLI was invoked with "." meaning "install in current directory".
|
|
42
|
+
* Different from projectNameFromArg which creates a subdirectory.
|
|
43
|
+
*/
|
|
44
|
+
installInCwd?: boolean;
|
|
45
|
+
/** Schema approach for content templates (code-first, visual) */
|
|
46
|
+
approach?: ProjectApproach;
|
|
47
|
+
/** Path to local templates directory (for development, bypasses GitHub download) */
|
|
48
|
+
localTemplatePath?: string;
|
|
49
|
+
/** Git branch to take a template from; unset, it comes from this CLI's release */
|
|
50
|
+
branch?: string;
|
|
51
|
+
/**
|
|
52
|
+
* Whether to add the Nextly skills for coding agents to the project.
|
|
53
|
+
* False skips them without asking; otherwise the developer is asked, and
|
|
54
|
+
* defaults mode adds them.
|
|
55
|
+
*/
|
|
56
|
+
skills?: boolean;
|
|
50
57
|
}
|
|
51
|
-
|
|
52
58
|
/**
|
|
53
59
|
* Main entry point for scaffolding Nextly in a Next.js project.
|
|
54
60
|
*
|
|
@@ -64,6 +70,6 @@ interface CreateNextlyOptions {
|
|
|
64
70
|
* 5. Database selection (sqlite, postgresql, mysql)
|
|
65
71
|
* 6. Database connection string (only for postgresql/mysql)
|
|
66
72
|
*/
|
|
67
|
-
declare function createNextly(options?: CreateNextlyOptions): Promise<void>;
|
|
68
|
-
|
|
69
|
-
|
|
73
|
+
export declare function createNextly(options?: CreateNextlyOptions): Promise<void>;
|
|
74
|
+
export type { CreateNextlyOptions };
|
|
75
|
+
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1 @@
|
|
|
1
|
-
|
|
2
|
-
export { createNextly } from './chunk-RPGFJSDP.mjs';
|
|
3
|
-
|
|
4
|
-
createRequire(import.meta.url);
|
|
5
|
-
//# sourceMappingURL=index.mjs.map
|
|
6
|
-
//# sourceMappingURL=index.mjs.map
|
|
1
|
+
export { t as createNextly } from "./create-nextly-5wiGMVI6.mjs";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-nextly-app",
|
|
3
|
-
"version": "0.0.2-alpha.
|
|
3
|
+
"version": "0.0.2-alpha.71",
|
|
4
4
|
"description": "CLI to scaffold Nextly in your Next.js project",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -20,34 +20,45 @@
|
|
|
20
20
|
"files": [
|
|
21
21
|
"dist",
|
|
22
22
|
"bin",
|
|
23
|
+
"skills",
|
|
23
24
|
"templates"
|
|
24
25
|
],
|
|
25
26
|
"dependencies": {
|
|
27
|
+
"@babel/parser": "^7.29.7",
|
|
26
28
|
"@clack/prompts": "^1.1.0",
|
|
27
29
|
"commander": "^14.0.2",
|
|
30
|
+
"dotenv-expand": "10.0.0",
|
|
28
31
|
"execa": "^9.6.0",
|
|
29
32
|
"fs-extra": "^11.3.0",
|
|
30
|
-
"ora": "^9.0.0",
|
|
31
33
|
"picocolors": "^1.1.1",
|
|
32
|
-
"
|
|
34
|
+
"picomatch": "^4.0.5",
|
|
35
|
+
"tar": "^7.5.19",
|
|
36
|
+
"yaml": "^2.8.3"
|
|
33
37
|
},
|
|
34
38
|
"devDependencies": {
|
|
39
|
+
"@babel/types": "^7.29.7",
|
|
40
|
+
"@nextlyhq/eslint-plugin": "0.0.2-alpha.71",
|
|
41
|
+
"@nextlyhq/module-specifiers": "0.0.2-alpha.71",
|
|
42
|
+
"@nextlyhq/telemetry": "0.0.2-alpha.71",
|
|
43
|
+
"@nextlyhq/tsconfig": "0.0.2-alpha.71",
|
|
44
|
+
"@testing-library/react": "^16.3.0",
|
|
35
45
|
"@types/fs-extra": "^11.0.4",
|
|
46
|
+
"@types/node": "^22.20.5",
|
|
47
|
+
"@types/picomatch": "^4.0.3",
|
|
36
48
|
"@types/tar": "^6.1.13",
|
|
37
|
-
"@
|
|
38
|
-
"@vitest/
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
-
"
|
|
42
|
-
"
|
|
49
|
+
"@vitest/coverage-v8": "^5.0.5",
|
|
50
|
+
"@vitest/ui": "^5.0.5",
|
|
51
|
+
"jsdom": "^27.1.0",
|
|
52
|
+
"oxc-parser": "^0.155.0",
|
|
53
|
+
"react": "19.3.1",
|
|
54
|
+
"react-dom": "19.3.1",
|
|
55
|
+
"tsdown": "^0.23.1",
|
|
56
|
+
"typescript": "7.0.2",
|
|
43
57
|
"vite-tsconfig-paths": "^5.1.4",
|
|
44
|
-
"vitest": "^
|
|
45
|
-
"@nextlyhq/eslint-config": "0.0.2-alpha.6",
|
|
46
|
-
"@nextlyhq/telemetry": "0.0.2-alpha.6",
|
|
47
|
-
"@nextlyhq/tsconfig": "0.0.2-alpha.6"
|
|
58
|
+
"vitest": "^5.0.5"
|
|
48
59
|
},
|
|
49
60
|
"engines": {
|
|
50
|
-
"node": ">=
|
|
61
|
+
"node": "^22.12.0 || >=24.0.0"
|
|
51
62
|
},
|
|
52
63
|
"keywords": [
|
|
53
64
|
"nextly",
|
|
@@ -74,11 +85,9 @@
|
|
|
74
85
|
},
|
|
75
86
|
"author": "Nextly <contact@nextlyhq.com> (https://nextlyhq.com)",
|
|
76
87
|
"scripts": {
|
|
77
|
-
"build": "
|
|
78
|
-
"dev": "
|
|
79
|
-
"check-types": "tsc --noEmit",
|
|
80
|
-
"lint": "eslint . --max-warnings 0",
|
|
81
|
-
"lint:fix": "eslint . --fix",
|
|
88
|
+
"build": "tsdown --config-loader tsx",
|
|
89
|
+
"dev": "tsdown --config-loader tsx --watch",
|
|
90
|
+
"check-types": "node ../../scripts/tsc.mjs --noEmit && node ../../scripts/tsc.mjs --noEmit -p tsconfig.tests.json",
|
|
82
91
|
"test": "vitest run --passWithNoTests",
|
|
83
92
|
"test:watch": "vitest",
|
|
84
93
|
"clean": "rimraf dist"
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: nextly-authentication
|
|
3
|
+
description: >-
|
|
4
|
+
Use when deciding who can sign in to a Nextly project and what they can do:
|
|
5
|
+
the first admin account, roles, access rules on collections, API keys for
|
|
6
|
+
other apps and scripts, checking the signed-in user in your own server
|
|
7
|
+
code, and extra fields on users. Also use when asked to add login or
|
|
8
|
+
sign-up to a Nextly site, so the answer matches what Nextly provides.
|
|
9
|
+
metadata:
|
|
10
|
+
nextly-version: 0.0.2-alpha.71
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# Authentication and access in Nextly
|
|
14
|
+
|
|
15
|
+
Checked against `nextly` 0.0.2-alpha.67 with Next.js 16.4. Run
|
|
16
|
+
`npx nextly --version`; if it prints a newer version, prefer
|
|
17
|
+
<https://nextlyhq.com/docs/guides/authentication> where it disagrees with this
|
|
18
|
+
skill.
|
|
19
|
+
|
|
20
|
+
## What is built in, and what is not
|
|
21
|
+
|
|
22
|
+
Nextly has its own email and password sign-in for the **people who run the
|
|
23
|
+
site**: admins, editors and authors who use the admin panel at `/admin`. It is
|
|
24
|
+
not built on NextAuth or any OAuth library, and it needs no setup beyond
|
|
25
|
+
`NEXTLY_SECRET`.
|
|
26
|
+
|
|
27
|
+
Its session cookies are scoped to `/admin`. So in this release Nextly does
|
|
28
|
+
**not** provide sign-in for the visitors of your public site, nor social or
|
|
29
|
+
OAuth login. If the site needs visitor accounts, use an auth library for the
|
|
30
|
+
site and keep Nextly for content; do not try to reuse the admin sign-in for
|
|
31
|
+
visitors.
|
|
32
|
+
|
|
33
|
+
In particular, do not build a sign-in form for your own pages on the Direct
|
|
34
|
+
API's `nextly.login()`. It is for trusted server code, not for signing people
|
|
35
|
+
in, and this release has no ready path for members-only pages. Protect those
|
|
36
|
+
with the auth library the site uses.
|
|
37
|
+
|
|
38
|
+
## The secret
|
|
39
|
+
|
|
40
|
+
`NEXTLY_SECRET` signs the session tokens. The installer generates one in
|
|
41
|
+
`.env`. In production it must be at least 32 characters; generate one with:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
openssl rand -base64 32
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Never commit it, and use a different one per environment.
|
|
48
|
+
|
|
49
|
+
## The first admin
|
|
50
|
+
|
|
51
|
+
Open `/admin/setup` once and create the first account. It gets the **Super
|
|
52
|
+
Admin** role, which passes every check. After that, setup is closed, and people
|
|
53
|
+
are added in the admin under **Users**. Under `next dev` the form needs nothing
|
|
54
|
+
else.
|
|
55
|
+
|
|
56
|
+
In production (from the release after 0.0.2-alpha.68), the form also asks for a
|
|
57
|
+
one-time setup token: `NEXTLY_SETUP_TOKEN` if you set one (at least 43
|
|
58
|
+
characters, 16 distinct), or the one printed to the server's output on a line
|
|
59
|
+
beginning `[nextly] SETUP TOKEN`. Or skip the browser and run
|
|
60
|
+
`npx nextly users:create-admin --email <email> --name <name>` with the
|
|
61
|
+
production environment, the password in `NEXTLY_ADMIN_PASSWORD` or typed at the
|
|
62
|
+
prompt. Self-registration (`auth.allowRegistration`, off by default) never
|
|
63
|
+
creates an admin.
|
|
64
|
+
|
|
65
|
+
Built-in roles: `super-admin`, `admin`, `editor`, `author`, `viewer`. Roles and
|
|
66
|
+
their permissions are managed in the admin under **Settings > Roles**.
|
|
67
|
+
|
|
68
|
+
## Access rules on a collection
|
|
69
|
+
|
|
70
|
+
Each operation takes `true`, `false` or a function. A function receives
|
|
71
|
+
`{ user, roles, permissions, operation, collection }`, where `roles` are role
|
|
72
|
+
slugs such as `"editor"` and `user` is `null` for a request with no session.
|
|
73
|
+
|
|
74
|
+
```ts
|
|
75
|
+
import { defineCollection, text } from "nextly/config";
|
|
76
|
+
import type { AccessControlFunction } from "nextly";
|
|
77
|
+
|
|
78
|
+
const signedIn: AccessControlFunction = ({ user }) => Boolean(user);
|
|
79
|
+
const editors: AccessControlFunction = ({ roles }) =>
|
|
80
|
+
roles.includes("admin") || roles.includes("editor");
|
|
81
|
+
|
|
82
|
+
export const Articles = defineCollection({
|
|
83
|
+
slug: "articles",
|
|
84
|
+
fields: [text({ name: "title", required: true })],
|
|
85
|
+
access: {
|
|
86
|
+
read: () => true,
|
|
87
|
+
create: signedIn,
|
|
88
|
+
update: editors,
|
|
89
|
+
delete: editors,
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
- A Super Admin passes every rule; test rules with another role.
|
|
95
|
+
- These rules are applied to requests to the admin API and to Direct API
|
|
96
|
+
reads made with `overrideAccess: false`. A plain server-side
|
|
97
|
+
`nextly.find(...)` is a trusted read that skips them.
|
|
98
|
+
- `read: () => true` does not make the admin API public. Every request to
|
|
99
|
+
`/admin/api/...` needs a session or an API key, and is refused with 401
|
|
100
|
+
otherwise. Public pages read content on the server with `getNextly` (the
|
|
101
|
+
`nextly-collections` skill).
|
|
102
|
+
|
|
103
|
+
To apply the rules for a particular user in server code:
|
|
104
|
+
|
|
105
|
+
```ts
|
|
106
|
+
const result = await nextly.find({
|
|
107
|
+
collection: "articles",
|
|
108
|
+
overrideAccess: false,
|
|
109
|
+
user: { id: userId },
|
|
110
|
+
});
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## API keys, for other apps and scripts
|
|
114
|
+
|
|
115
|
+
Create keys in the admin under **Settings > API Keys**. The full key is shown
|
|
116
|
+
once. Send it as a bearer token:
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
curl -H "Authorization: Bearer $NEXTLY_API_KEY" \
|
|
120
|
+
"https://example.com/admin/api/collections/posts/entries?limit=10"
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
A **Read-only** key can read and is refused writes (403). **Full access**
|
|
124
|
+
carries its creator's permissions; **Role-based** carries the permissions of a
|
|
125
|
+
role you choose. Keys can expire and be revoked. Keep keys in environment
|
|
126
|
+
variables, never in client code.
|
|
127
|
+
|
|
128
|
+
## The signed-in user in your own server code
|
|
129
|
+
|
|
130
|
+
```ts
|
|
131
|
+
// src/app/admin/me/route.ts: the session cookie only reaches /admin paths
|
|
132
|
+
import { getNextly } from "nextly";
|
|
133
|
+
import { getSession, isSuperAdmin } from "nextly/auth";
|
|
134
|
+
|
|
135
|
+
import nextlyConfig from "../../../../nextly.config";
|
|
136
|
+
|
|
137
|
+
export async function GET(request: Request) {
|
|
138
|
+
// Start Nextly first: this route may be the first request a server handles.
|
|
139
|
+
await getNextly({ config: nextlyConfig });
|
|
140
|
+
const session = await getSession(request, process.env.NEXTLY_SECRET ?? "");
|
|
141
|
+
if (!session.authenticated) {
|
|
142
|
+
return Response.json({ error: "Not signed in" }, { status: 401 });
|
|
143
|
+
}
|
|
144
|
+
return Response.json({
|
|
145
|
+
email: session.user.email,
|
|
146
|
+
superAdmin: await isSuperAdmin(session.user.id),
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
- The route must live under `/admin`, or the browser never sends the session
|
|
152
|
+
cookie to it.
|
|
153
|
+
- `session.user.roleIds` holds role **IDs**, not role slugs, so
|
|
154
|
+
`hasRole(user, "editor")` from `nextly/auth` does not match by slug in this
|
|
155
|
+
release. Use `isSuperAdmin(user.id)`, or put role decisions in collection
|
|
156
|
+
access rules, where `roles` are slugs.
|
|
157
|
+
|
|
158
|
+
## Extra fields on users
|
|
159
|
+
|
|
160
|
+
```ts
|
|
161
|
+
import { defineConfig, text, textarea } from "nextly/config";
|
|
162
|
+
|
|
163
|
+
export default defineConfig({
|
|
164
|
+
users: {
|
|
165
|
+
fields: [
|
|
166
|
+
textarea({ name: "bio", maxLength: 500 }),
|
|
167
|
+
text({ name: "avatarUrl" }),
|
|
168
|
+
],
|
|
169
|
+
},
|
|
170
|
+
// collections, typescript, ...
|
|
171
|
+
});
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Read users with `nextly.users.find()`, not `find({ collection: "users" })`.
|
|
175
|
+
|
|
176
|
+
## Password reset and verification emails
|
|
177
|
+
|
|
178
|
+
These need an email provider. Without one, tokens come back in the API
|
|
179
|
+
response as a development fallback. See
|
|
180
|
+
<https://nextlyhq.com/docs/guides/email>.
|
|
181
|
+
|
|
182
|
+
## Check it worked
|
|
183
|
+
|
|
184
|
+
- `/admin/setup` creates the first account, and `/admin` signs in.
|
|
185
|
+
- A request to `/admin/api/collections/<slug>/entries` without a session or
|
|
186
|
+
key answers 401.
|
|
187
|
+
- A read-only API key reads entries and gets 403 on a write.
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: nextly-collections
|
|
3
|
+
description: >-
|
|
4
|
+
Use when defining or changing content types in a Nextly project in code:
|
|
5
|
+
writing a collection with defineCollection, choosing field types, adding
|
|
6
|
+
draft and published status, registering it in nextly.config.ts, generating
|
|
7
|
+
TypeScript types, creating a migration, and reading entries from a Next.js
|
|
8
|
+
Server Component with getNextly and find. Also use when a collection does
|
|
9
|
+
not appear in the admin, a field change does not reach the database, or a
|
|
10
|
+
page reading content fails to build.
|
|
11
|
+
metadata:
|
|
12
|
+
nextly-version: 0.0.2-alpha.71
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# Define a collection in Nextly
|
|
16
|
+
|
|
17
|
+
A collection is a content type with many entries: posts, products, authors.
|
|
18
|
+
Each one becomes a database table, an admin list and edit screen, and a type
|
|
19
|
+
you can query. Collections can also be made in the admin's visual Schema
|
|
20
|
+
Builder; this skill covers the code-first way.
|
|
21
|
+
|
|
22
|
+
Checked against `nextly` 0.0.2-alpha.67 with Next.js 16.4. Run
|
|
23
|
+
`npx nextly --version`; if it prints a newer version, prefer
|
|
24
|
+
<https://nextlyhq.com/docs> where it disagrees with this skill. Nextly must
|
|
25
|
+
already be installed (the `nextly-install` skill), with `esbuild` among the
|
|
26
|
+
dev dependencies.
|
|
27
|
+
|
|
28
|
+
## 1. Write the collection
|
|
29
|
+
|
|
30
|
+
`src/collections/posts.ts`:
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
import {
|
|
34
|
+
defineCollection,
|
|
35
|
+
text,
|
|
36
|
+
textarea,
|
|
37
|
+
richText,
|
|
38
|
+
date,
|
|
39
|
+
select,
|
|
40
|
+
} from "nextly/config";
|
|
41
|
+
|
|
42
|
+
export const Posts = defineCollection({
|
|
43
|
+
slug: "posts",
|
|
44
|
+
labels: { singular: "Post", plural: "Posts" },
|
|
45
|
+
fields: [
|
|
46
|
+
text({ name: "title", required: true }),
|
|
47
|
+
text({ name: "slug", required: true, unique: true }),
|
|
48
|
+
textarea({ name: "excerpt" }),
|
|
49
|
+
richText({ name: "content" }),
|
|
50
|
+
date({ name: "publishedAt" }),
|
|
51
|
+
select({
|
|
52
|
+
name: "category",
|
|
53
|
+
options: [
|
|
54
|
+
{ label: "News", value: "news" },
|
|
55
|
+
{ label: "Guide", value: "guide" },
|
|
56
|
+
],
|
|
57
|
+
}),
|
|
58
|
+
],
|
|
59
|
+
// Draft and Published, with Save draft and Publish buttons in the admin.
|
|
60
|
+
status: true,
|
|
61
|
+
admin: { useAsTitle: "title" },
|
|
62
|
+
access: {
|
|
63
|
+
read: () => true,
|
|
64
|
+
create: ({ user }) => Boolean(user),
|
|
65
|
+
update: ({ user }) => Boolean(user),
|
|
66
|
+
delete: ({ roles }) => roles.includes("admin"),
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Rules that matter:
|
|
72
|
+
|
|
73
|
+
- Import from `nextly/config` in collection files and in `nextly.config.ts`.
|
|
74
|
+
- Use **relative imports** in these files, never the `@/` alias. The Nextly
|
|
75
|
+
CLI loads them with Node.js, which does not know Next.js aliases.
|
|
76
|
+
- `slug` is the table and API name: lowercase, stable. Renaming it later is a
|
|
77
|
+
migration, not an edit.
|
|
78
|
+
- `id`, `createdAt` and `updatedAt` are added for you; do not declare them.
|
|
79
|
+
Declare `title` and `slug` yourself, as above, or the admin shows no input
|
|
80
|
+
for them and the slug is not unique. The admin fills an empty `slug` from
|
|
81
|
+
the title.
|
|
82
|
+
- Field helpers in this release: `text`, `textarea`, `richText`, `email`,
|
|
83
|
+
`password`, `code`, `number`, `checkbox`, `date`, `select`, `radio`,
|
|
84
|
+
`chips`, `upload`, `relationship`, `repeater`, `array`, `group`, `json`.
|
|
85
|
+
`relationship({ name: "author", relationTo: "users" })` links entries;
|
|
86
|
+
add `hasMany: true` for several. `upload({ name: "image", relationTo: "media" })`
|
|
87
|
+
links a media file.
|
|
88
|
+
- Access rules are explained in the `nextly-authentication` skill.
|
|
89
|
+
|
|
90
|
+
## 2. Register it
|
|
91
|
+
|
|
92
|
+
`nextly.config.ts`:
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
import { defineConfig } from "nextly/config";
|
|
96
|
+
|
|
97
|
+
import { Posts } from "./src/collections/posts";
|
|
98
|
+
|
|
99
|
+
export default defineConfig({
|
|
100
|
+
collections: [Posts],
|
|
101
|
+
singles: [],
|
|
102
|
+
typescript: {
|
|
103
|
+
outputFile: "./src/types/generated/nextly-types.ts",
|
|
104
|
+
},
|
|
105
|
+
});
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## 3. Apply it in development
|
|
109
|
+
|
|
110
|
+
Restart `next dev` after adding or changing a collection. The change is
|
|
111
|
+
applied to the development database on the first request after the restart:
|
|
112
|
+
the log says `Code-first sync completed`. On Next.js 16.4 the in-process
|
|
113
|
+
reload does not pick up a changed field without a restart.
|
|
114
|
+
|
|
115
|
+
Then open `/admin`: the collection is in the sidebar, with list and create
|
|
116
|
+
screens.
|
|
117
|
+
|
|
118
|
+
## 4. Generate types
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
npx nextly generate:types
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
This writes the file named in `typescript.outputFile`, and Zod schemas under
|
|
125
|
+
`src/db/schemas/collections/zod/` (`db/schemas/collections/zod/` without a
|
|
126
|
+
`src/` folder). With them, `find({ collection: "posts" })` is typed; without
|
|
127
|
+
them, entries are loosely typed and fields read as `unknown`. Regenerate in
|
|
128
|
+
the same change as every schema edit.
|
|
129
|
+
|
|
130
|
+
The Zod schemas import `z` from `nextly/zod`, so the project needs no `zod`
|
|
131
|
+
dependency of its own. To combine them with your own Zod code, import `z`
|
|
132
|
+
from `nextly/zod` there too.
|
|
133
|
+
|
|
134
|
+
## 5. Read entries in a page
|
|
135
|
+
|
|
136
|
+
Content is read on the server with the Direct API. Do not build an API route
|
|
137
|
+
to fetch your own content.
|
|
138
|
+
|
|
139
|
+
```tsx
|
|
140
|
+
// src/app/posts/page.tsx
|
|
141
|
+
import { getNextly } from "nextly";
|
|
142
|
+
import { cachedFind, nextlyTags } from "nextly/runtime";
|
|
143
|
+
|
|
144
|
+
import nextlyConfig from "../../../nextly.config";
|
|
145
|
+
|
|
146
|
+
async function getPublishedPosts() {
|
|
147
|
+
return cachedFind(
|
|
148
|
+
async () => {
|
|
149
|
+
const nextly = await getNextly({ config: nextlyConfig });
|
|
150
|
+
const { items } = await nextly.find({
|
|
151
|
+
collection: "posts",
|
|
152
|
+
status: "published",
|
|
153
|
+
limit: 10,
|
|
154
|
+
});
|
|
155
|
+
return items;
|
|
156
|
+
},
|
|
157
|
+
{ tags: nextlyTags("posts"), keyParts: ["posts", "list"] }
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export default async function PostsPage() {
|
|
162
|
+
const posts = await getPublishedPosts();
|
|
163
|
+
return (
|
|
164
|
+
<ul>
|
|
165
|
+
{posts.map(post => (
|
|
166
|
+
<li key={String(post.id)}>{String(post.title)}</li>
|
|
167
|
+
))}
|
|
168
|
+
</ul>
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
- Without a `src/` folder the page is `app/posts/page.tsx`, importing
|
|
174
|
+
`"../../nextly.config"`.
|
|
175
|
+
- `find()` returns `{ items, meta }`. `meta` holds the totals and pages.
|
|
176
|
+
- `status: "published"` keeps drafts off the page. Without it a trusted
|
|
177
|
+
server read returns drafts too.
|
|
178
|
+
- `cachedFind` with `nextlyTags("posts")` caches the result and clears it when
|
|
179
|
+
a post is saved, so the page updates on publish. It also lets the page build
|
|
180
|
+
in an app with `cacheComponents: true`, where an uncached database read
|
|
181
|
+
outside `<Suspense>` fails `next build`.
|
|
182
|
+
- `keyParts` names the cache entry, and everyone whose read uses the same key
|
|
183
|
+
gets the same rows. Cache only reads that are the same for every reader:
|
|
184
|
+
published content of a collection with no read rule, on the collection or
|
|
185
|
+
on any of its fields. Do not cache a read that applies a reader's access
|
|
186
|
+
rules (`overrideAccess: false` with a `user`): the reader's roles and
|
|
187
|
+
permissions can change without any content changing, so no tag clears the
|
|
188
|
+
entry. Read it uncached inside `<Suspense>` instead.
|
|
189
|
+
- Rich text comes back as Lexical JSON. Pass `richTextFormat: "html"` to
|
|
190
|
+
`find()` to get HTML instead.
|
|
191
|
+
- Users are not a normal collection: read them with `nextly.users.find()`,
|
|
192
|
+
which supports pagination and `search` but not `where`.
|
|
193
|
+
|
|
194
|
+
## 6. Make a migration before deploying
|
|
195
|
+
|
|
196
|
+
Development applies changes directly; production applies migration files.
|
|
197
|
+
When the change is ready to ship:
|
|
198
|
+
|
|
199
|
+
```bash
|
|
200
|
+
npx nextly migrate:create add_posts
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
This compares the config with the last migration's snapshot and writes
|
|
204
|
+
`src/db/migrations/<timestamp>_add_posts.sql` plus a snapshot under
|
|
205
|
+
`src/db/migrations/meta/`. Read the SQL, then commit both with the config
|
|
206
|
+
change. `npx nextly migrate` applies pending migrations; the `nextly-deploy`
|
|
207
|
+
skill runs it on every deploy. Once a migration is shared, never edit it: add
|
|
208
|
+
a new one.
|
|
209
|
+
|
|
210
|
+
## Check it worked
|
|
211
|
+
|
|
212
|
+
- The admin lists the collection and saves an entry.
|
|
213
|
+
- `npx nextly generate:types` succeeds and `npx tsc --noEmit` is clean.
|
|
214
|
+
- The page shows a published entry, and a draft does not appear.
|
|
215
|
+
- `next build` succeeds.
|