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.
Files changed (75) hide show
  1. package/README.md +36 -9
  2. package/dist/cli.cjs +3047 -32460
  3. package/dist/cli.cjs.map +1 -1
  4. package/dist/cli.d.cts +1 -1
  5. package/dist/cli.d.ts +1 -1
  6. package/dist/cli.mjs +3048 -3602
  7. package/dist/cli.mjs.map +1 -1
  8. package/dist/create-nextly-5wiGMVI6.mjs +52550 -0
  9. package/dist/create-nextly-5wiGMVI6.mjs.map +1 -0
  10. package/dist/create-nextly-BbKGDrEC.cjs +52628 -0
  11. package/dist/create-nextly-BbKGDrEC.cjs.map +1 -0
  12. package/dist/index.cjs +3 -24861
  13. package/dist/index.d.cts +40 -34
  14. package/dist/index.d.ts +40 -34
  15. package/dist/index.mjs +1 -6
  16. package/package.json +28 -19
  17. package/skills/nextly-authentication/SKILL.md +187 -0
  18. package/skills/nextly-collections/SKILL.md +215 -0
  19. package/skills/nextly-deploy/SKILL.md +248 -0
  20. package/skills/nextly-install/SKILL.md +121 -0
  21. package/templates/base/.env.example +21 -6
  22. package/templates/base/AGENTS.md.template +226 -0
  23. package/templates/base/CLAUDE.md.template +1 -0
  24. package/templates/base/gitignore +42 -0
  25. package/templates/base/next.config.ts +11 -17
  26. package/templates/base/src/app/admin/[[...params]]/page.tsx +3 -0
  27. package/templates/base/src/app/admin/api/media/[[...path]]/route.ts +26 -0
  28. package/templates/base/src/app/globals.css +15 -2
  29. package/templates/base/src/app/layout.tsx +32 -18
  30. package/templates/base/src/lib/site-url.ts +55 -0
  31. package/templates/base/src/types/generated/nextly-types.ts +6 -4
  32. package/templates/base/src/types/generated/plugin-admin-components.generated.ts +6 -0
  33. package/templates/base/src/types/generated/plugin-admin-imports.generated.ts +32 -0
  34. package/templates/base/tsconfig.json +8 -1
  35. package/templates/blank/src/{components → app/(frontend)/components}/ThemeToggle.tsx +55 -24
  36. package/templates/blank/src/app/(frontend)/page.tsx +2 -2
  37. package/templates/blank/src/app/globals.css +37 -1
  38. package/templates/blank/src/app/layout.tsx +28 -18
  39. package/templates/blank/src/instrumentation.ts +32 -0
  40. package/templates/plugin/AGENTS.md.template +125 -0
  41. package/templates/plugin/CLAUDE.md.template +1 -0
  42. package/templates/plugin/README.md +48 -0
  43. package/templates/plugin/dev/.env.example +11 -0
  44. package/templates/plugin/dev/next.config.ts +43 -0
  45. package/templates/plugin/dev/nextly.config.ts +17 -0
  46. package/templates/plugin/dev/src/app/admin/[[...params]]/page.tsx +24 -0
  47. package/templates/plugin/dev/src/app/admin/api/[[...params]]/route.ts +19 -0
  48. package/templates/plugin/dev/src/app/layout.tsx +13 -0
  49. package/templates/plugin/dev/src/instrumentation.ts +25 -0
  50. package/templates/plugin/dev/src/seed-dev-user.ts +51 -0
  51. package/templates/plugin/dev/src/types/generated/plugin-admin-components.generated.ts +8 -0
  52. package/templates/plugin/dev/src/types/generated/plugin-admin-imports.generated.ts +37 -0
  53. package/templates/plugin/dev/tsconfig.json +22 -0
  54. package/templates/plugin/eslint.config.mjs +19 -0
  55. package/templates/plugin/gitignore +13 -0
  56. package/templates/plugin/src/admin/SettingsPage.tsx +57 -0
  57. package/templates/plugin/src/admin/index.ts +24 -0
  58. package/templates/plugin/src/collections/example.ts +13 -0
  59. package/templates/plugin/src/index.ts +6 -0
  60. package/templates/plugin/src/plugin.test.ts +35 -0
  61. package/templates/plugin/src/plugin.ts +48 -0
  62. package/templates/plugin/src/types.ts +17 -0
  63. package/templates/plugin/template.json +14 -0
  64. package/templates/plugin/tsconfig.json +18 -0
  65. package/templates/plugin/tsdown.config.ts +30 -0
  66. package/templates/plugin/vitest.config.ts +27 -0
  67. package/dist/chunk-RPGFJSDP.mjs +0 -28835
  68. package/dist/chunk-RPGFJSDP.mjs.map +0 -1
  69. package/dist/index.cjs.map +0 -1
  70. package/dist/index.mjs.map +0 -1
  71. package/templates/blank/src/access/README.md +0 -22
  72. package/templates/blank/src/collections/README.md +0 -22
  73. package/templates/blank/src/lib/README.md +0 -15
  74. package/templates/blank/src/singles/README.md +0 -19
  75. /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
- /** Working directory (defaults to process.cwd()) */
22
- cwd?: string;
23
- /** Skip interactive prompts and use defaults */
24
- defaults?: boolean;
25
- /** Pre-selected project type / template name */
26
- projectType?: ProjectType;
27
- /** Pre-selected database type */
28
- database?: DatabaseType;
29
- /** Skip dependency installation (useful for local testing before packages are published) */
30
- skipInstall?: boolean;
31
- /** Use yalc for local package installation (for testing before npm publish) */
32
- useYalc?: boolean;
33
- /**
34
- * Project name derived from the CLI's positional `[directory]` argument.
35
- * Must be a bare directory name (no slashes). Callers pass the basename
36
- * of the user-provided argument.
37
- */
38
- projectNameFromArg?: string;
39
- /**
40
- * When true, the CLI was invoked with "." meaning "install in current directory".
41
- * Different from projectNameFromArg which creates a subdirectory.
42
- */
43
- installInCwd?: boolean;
44
- /** Schema approach for content templates (code-first, visual) */
45
- approach?: ProjectApproach;
46
- /** Path to local templates directory (for development, bypasses GitHub download) */
47
- localTemplatePath?: string;
48
- /** Git branch for template download from GitHub (defaults to "main") */
49
- branch?: string;
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
- export { type CreateNextlyOptions, createNextly };
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
- /** Working directory (defaults to process.cwd()) */
22
- cwd?: string;
23
- /** Skip interactive prompts and use defaults */
24
- defaults?: boolean;
25
- /** Pre-selected project type / template name */
26
- projectType?: ProjectType;
27
- /** Pre-selected database type */
28
- database?: DatabaseType;
29
- /** Skip dependency installation (useful for local testing before packages are published) */
30
- skipInstall?: boolean;
31
- /** Use yalc for local package installation (for testing before npm publish) */
32
- useYalc?: boolean;
33
- /**
34
- * Project name derived from the CLI's positional `[directory]` argument.
35
- * Must be a bare directory name (no slashes). Callers pass the basename
36
- * of the user-provided argument.
37
- */
38
- projectNameFromArg?: string;
39
- /**
40
- * When true, the CLI was invoked with "." meaning "install in current directory".
41
- * Different from projectNameFromArg which creates a subdirectory.
42
- */
43
- installInCwd?: boolean;
44
- /** Schema approach for content templates (code-first, visual) */
45
- approach?: ProjectApproach;
46
- /** Path to local templates directory (for development, bypasses GitHub download) */
47
- localTemplatePath?: string;
48
- /** Git branch for template download from GitHub (defaults to "main") */
49
- branch?: string;
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
- export { type CreateNextlyOptions, createNextly };
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
- import { createRequire } from 'module';
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.7",
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
- "tar": "^7.4.0"
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
- "@types/node": "^20.19.17",
38
- "@vitest/coverage-v8": "^4.0.8",
39
- "@vitest/ui": "^4.0.8",
40
- "eslint": "^9.34.0",
41
- "tsup": "^8.5.0",
42
- "typescript": "^5.9.3",
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": "^4.0.8",
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": ">=20.0.0"
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": "tsup",
78
- "dev": "tsup --watch",
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.