create-nextly-app 0.0.2-alpha.65 → 0.0.2-alpha.67
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 +29 -8
- package/dist/{chunk-I2U7KBH6.mjs → chunk-XCYSH46I.mjs} +36805 -10917
- package/dist/chunk-XCYSH46I.mjs.map +1 -0
- package/dist/cli.cjs +27693 -1892
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.mjs +17 -106
- package/dist/cli.mjs.map +1 -1
- package/dist/index.cjs +36591 -10708
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.mjs +1 -1
- package/package.json +18 -7
- package/skills/nextly-authentication/SKILL.md +177 -0
- package/skills/nextly-collections/SKILL.md +215 -0
- package/skills/nextly-deploy/SKILL.md +246 -0
- package/skills/nextly-install/SKILL.md +122 -0
- package/templates/base/.env.example +3 -5
- package/templates/base/AGENTS.md.template +83 -15
- package/templates/base/src/types/generated/nextly-types.ts +6 -4
- package/templates/blank/src/app/(frontend)/components/ThemeToggle.tsx +55 -24
- package/templates/blank/src/instrumentation.ts +27 -0
- package/templates/plugin/AGENTS.md.template +33 -4
- package/templates/plugin/README.md +4 -0
- package/templates/plugin/dev/.env.example +5 -2
- package/templates/plugin/dev/src/app/admin/[[...params]]/page.tsx +14 -2
- package/templates/plugin/dev/src/instrumentation.ts +20 -0
- package/templates/plugin/dev/src/seed-dev-user.ts +51 -0
- package/templates/plugin/gitignore +5 -2
- package/templates/plugin/src/plugin.test.ts +9 -2
- package/templates/plugin/vitest.config.ts +19 -0
- package/dist/chunk-I2U7KBH6.mjs.map +0 -1
- package/templates/plugin/dev/instrumentation.ts +0 -10
|
@@ -40,10 +40,10 @@ working on an app.
|
|
|
40
40
|
|
|
41
41
|
- **Plugin APIs come from `@nextlyhq/plugin-sdk`; schema APIs come from
|
|
42
42
|
`nextly/config`.** The SDK is the stability boundary for everything a plugin
|
|
43
|
-
does AS a plugin: `definePlugin`, `
|
|
44
|
-
`
|
|
45
|
-
from `@nextlyhq/plugin-sdk/
|
|
46
|
-
|
|
43
|
+
does AS a plugin: `definePlugin`, field factories, `pluginField` and
|
|
44
|
+
`NextlyError` from `@nextlyhq/plugin-sdk`; `registerComponents` and
|
|
45
|
+
`registerKnownPlugin` from `@nextlyhq/plugin-sdk/admin`; `createTestNextly`
|
|
46
|
+
from `@nextlyhq/plugin-sdk/testing`. Collections and singles are defined the same way an app defines
|
|
47
47
|
them, with `defineCollection` from `nextly/config` — which is what
|
|
48
48
|
`src/collections/example.ts` does.
|
|
49
49
|
|
|
@@ -73,6 +73,8 @@ in-memory SQLite with your plugin registered, so a test exercises the same
|
|
|
73
73
|
lifecycle a host would run:
|
|
74
74
|
|
|
75
75
|
```ts
|
|
76
|
+
import { createTestNextly } from "@nextlyhq/plugin-sdk/testing";
|
|
77
|
+
|
|
76
78
|
const t = await createTestNextly({ plugins: [myPlugin()] });
|
|
77
79
|
```
|
|
78
80
|
|
|
@@ -84,6 +86,33 @@ those slugs a second time as code-owned entities and boot fails with
|
|
|
84
86
|
Assert on behaviour through `t.nextly` rather than on the plugin object: what a
|
|
85
87
|
host observes is the contract, and the object shape is not.
|
|
86
88
|
|
|
89
|
+
## Which Nextly this is, and where its docs are
|
|
90
|
+
|
|
91
|
+
An agent working here will otherwise answer from whatever it remembers of some
|
|
92
|
+
other version. Ask the project instead:
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
{{execCommand}} nextly --version # the running core version
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
That is the same version the plugin resolver validates a plugin's `nextly`
|
|
99
|
+
range against, so a compatibility answer and a reported version cannot
|
|
100
|
+
disagree. The dependency range this project pins is in `package.json` under
|
|
101
|
+
`nextly`; the resolved version is what the command above prints.
|
|
102
|
+
|
|
103
|
+
**The authoritative documentation is <https://nextlyhq.com/docs>.** Two files
|
|
104
|
+
are published for machine readers and are the ones to fetch:
|
|
105
|
+
|
|
106
|
+
| file | what it is |
|
|
107
|
+
| ---- | ---------- |
|
|
108
|
+
| <https://nextlyhq.com/llms.txt> | the index — every documentation page, with a one-line summary |
|
|
109
|
+
| <https://nextlyhq.com/llms-full.txt> | the same documentation as one file |
|
|
110
|
+
|
|
111
|
+
Both are generated from the pages the site publishes, so they cannot describe a
|
|
112
|
+
page that does not exist. Neither is version-stamped: they describe the CURRENT
|
|
113
|
+
release, so check the version above before trusting a detail against an older
|
|
114
|
+
pin.
|
|
115
|
+
|
|
87
116
|
## Conventions worth keeping
|
|
88
117
|
|
|
89
118
|
- Version `nextly` compatibility in the factory's `nextly` range, and widen it
|
|
@@ -13,6 +13,10 @@ Your plugin lives in `src/`. The `dev/` folder is a minimal Nextly app on SQLite
|
|
|
13
13
|
that registers this plugin so you can exercise it in a real admin with hot-reload.
|
|
14
14
|
Editing files under `src/` reloads the playground. **`dev/` is never published.**
|
|
15
15
|
|
|
16
|
+
The playground signs you in automatically as `dev@nextly.local` (password
|
|
17
|
+
`DevPassword123!`). That user is created on the first `pnpm dev`, and only
|
|
18
|
+
under `next dev`; production builds ignore the auto-login setting.
|
|
19
|
+
|
|
16
20
|
## Test
|
|
17
21
|
|
|
18
22
|
```bash
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
# Dev playground env.
|
|
1
|
+
# Dev playground env. create-nextly-app writes dev/.env from this file;
|
|
2
|
+
# copy it again to start over.
|
|
2
3
|
DB_DIALECT=sqlite
|
|
3
|
-
|
|
4
|
+
# Relative to the plugin root, where `pnpm dev` runs (`next dev dev` keeps
|
|
5
|
+
# that working directory), so the database lands in dev/.
|
|
6
|
+
DATABASE_URL=file:./dev/nextly-dev.db
|
|
4
7
|
|
|
5
8
|
# Any non-empty secret works for local dev (sign/verify session cookies).
|
|
6
9
|
NEXTLY_SECRET=dev-secret-change-me
|
|
@@ -1,9 +1,21 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import "@nextlyhq/admin/style.css";
|
|
4
|
-
import { RootLayout } from "@nextlyhq/admin";
|
|
4
|
+
import { ErrorBoundary, QueryProvider, RootLayout } from "@nextlyhq/admin";
|
|
5
5
|
|
|
6
6
|
// Mounts the Nextly admin shell. Your plugin's menu/pages/settings appear here.
|
|
7
|
+
// The admin's data hooks read their client from QueryProvider, so RootLayout
|
|
8
|
+
// renders only inside it, as in an app scaffold.
|
|
7
9
|
export default function AdminPage() {
|
|
8
|
-
return
|
|
10
|
+
return (
|
|
11
|
+
<ErrorBoundary
|
|
12
|
+
onError={(error, errorInfo) => {
|
|
13
|
+
console.error("Admin error:", error, errorInfo);
|
|
14
|
+
}}
|
|
15
|
+
>
|
|
16
|
+
<QueryProvider>
|
|
17
|
+
<RootLayout />
|
|
18
|
+
</QueryProvider>
|
|
19
|
+
</ErrorBoundary>
|
|
20
|
+
);
|
|
9
21
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// Warm up Nextly once per worker at startup (Node.js runtime only) so admin/API
|
|
2
|
+
// handlers never run before init and schema boot-apply runs once, not per request.
|
|
3
|
+
// Imported dynamically + guarded so nextly stays out of the Edge bundle. It sits
|
|
4
|
+
// in src/ beside app/, where Next.js looks for it when an app lives under src/.
|
|
5
|
+
export async function register(): Promise<void> {
|
|
6
|
+
if (process.env.NEXT_RUNTIME !== "nodejs") return;
|
|
7
|
+
|
|
8
|
+
const { createRegister, getService } = await import("nextly");
|
|
9
|
+
const { default: config } = await import("../nextly.config");
|
|
10
|
+
await createRegister(config)();
|
|
11
|
+
|
|
12
|
+
// Create the user admin.devAutoLogin signs in as (see seed-dev-user.ts).
|
|
13
|
+
// After boot, because boot seeds every permission and the Super Admin role
|
|
14
|
+
// created here takes all that exist.
|
|
15
|
+
const { seedSuperAdmin } = await import("nextly/database/seeders");
|
|
16
|
+
const { seedDevUser } = await import("./seed-dev-user");
|
|
17
|
+
await seedDevUser(config.admin?.devAutoLogin, options =>
|
|
18
|
+
seedSuperAdmin(getService("adapter"), options)
|
|
19
|
+
);
|
|
20
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Create the account `admin.devAutoLogin` signs in as.
|
|
3
|
+
*
|
|
4
|
+
* Auto-login signs in a user that already exists and never creates one, so
|
|
5
|
+
* without this the first /admin visit lands on the setup wizard. The seeder is
|
|
6
|
+
* passed in by instrumentation.ts, which keeps this file free of imports and
|
|
7
|
+
* lets it run on its own.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/** The account `admin.devAutoLogin` names, as nextly.config.ts declares it. */
|
|
11
|
+
export type DevAutoLogin = false | { email: string; password?: string };
|
|
12
|
+
|
|
13
|
+
/** Creates a super-admin, skipping one that already exists. */
|
|
14
|
+
export type SeedSuperAdmin = (options: {
|
|
15
|
+
email: string;
|
|
16
|
+
password: string;
|
|
17
|
+
name: string;
|
|
18
|
+
silent: boolean;
|
|
19
|
+
}) => Promise<{ success: boolean; errorMessages?: string[] }>;
|
|
20
|
+
|
|
21
|
+
export async function seedDevUser(
|
|
22
|
+
login: DevAutoLogin | undefined,
|
|
23
|
+
seed: SeedSuperAdmin
|
|
24
|
+
): Promise<void> {
|
|
25
|
+
// `next dev` only: no credentials in a production build, and tests bring
|
|
26
|
+
// their own fixtures.
|
|
27
|
+
if (process.env.NODE_ENV !== "development" || !login) return;
|
|
28
|
+
|
|
29
|
+
// Auto-login needs no password, but creating the account does.
|
|
30
|
+
if (!login.password) {
|
|
31
|
+
console.warn(
|
|
32
|
+
`[dev] admin.devAutoLogin names ${login.email} without a password, so that user is not created.`
|
|
33
|
+
);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const result = await seed({
|
|
38
|
+
email: login.email,
|
|
39
|
+
password: login.password,
|
|
40
|
+
name: "Dev User",
|
|
41
|
+
silent: true,
|
|
42
|
+
});
|
|
43
|
+
if (!result.success) {
|
|
44
|
+
// Otherwise the only sign is the setup wizard, with nothing saying why.
|
|
45
|
+
console.warn(
|
|
46
|
+
`[dev] Could not create the auto-login user ${login.email}: ${
|
|
47
|
+
result.errorMessages?.join("; ") ?? "unknown error"
|
|
48
|
+
}`
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -4,7 +4,10 @@ dist
|
|
|
4
4
|
|
|
5
5
|
# Embedded dev playground — never published, never committed build output
|
|
6
6
|
dev/.next
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
# SQLite files, and the journal and WAL files beside them, wherever they land.
|
|
8
|
+
*.db
|
|
9
|
+
*.db-journal
|
|
10
|
+
*.db-wal
|
|
11
|
+
*.db-shm
|
|
9
12
|
.env
|
|
10
13
|
.env.local
|
|
@@ -20,9 +20,16 @@ afterEach(async () => {
|
|
|
20
20
|
it("boots and exposes the plugin's example collection (real table)", async () => {
|
|
21
21
|
// Creating an entry proves the contributed collection got a real table and
|
|
22
22
|
// the plugin's lifecycle ran. Replace with assertions for your own behavior.
|
|
23
|
-
|
|
23
|
+
// The Direct API takes one argument object per call.
|
|
24
|
+
const created = await t.nextly.create({
|
|
25
|
+
collection: "examples",
|
|
26
|
+
data: { title: "First example" },
|
|
27
|
+
});
|
|
24
28
|
expect(created.item.id).toBeDefined();
|
|
25
29
|
|
|
26
|
-
const fetched = await t.nextly.
|
|
30
|
+
const fetched = await t.nextly.findByID({
|
|
31
|
+
collection: "examples",
|
|
32
|
+
id: String(created.item.id),
|
|
33
|
+
});
|
|
27
34
|
expect(fetched?.title).toBe("First example");
|
|
28
35
|
});
|
|
@@ -4,5 +4,24 @@ export default defineConfig({
|
|
|
4
4
|
test: {
|
|
5
5
|
environment: "node",
|
|
6
6
|
include: ["src/**/*.test.ts"],
|
|
7
|
+
/*
|
|
8
|
+
* Sized for an instance boot, because that is what this suite does.
|
|
9
|
+
*
|
|
10
|
+
* `plugin.test.ts` calls `createTestNextly` in `beforeEach`, which builds a
|
|
11
|
+
* DI container, registers the plugin's schema and runs auto-sync against a
|
|
12
|
+
* real SQLite database. Vitest's defaults are sized for a unit test that
|
|
13
|
+
* touches none of that: 5s for a case and 10s for a hook. A boot is about a
|
|
14
|
+
* second and a half on a warm machine, and the first run of a freshly
|
|
15
|
+
* scaffolded project is the least warm moment there is - a cold install, no
|
|
16
|
+
* build cache, whatever the laptop or CI container is doing.
|
|
17
|
+
*
|
|
18
|
+
* This is the first command a new plugin author runs, so a timeout here
|
|
19
|
+
* reads as "the scaffold ships a broken test" rather than "the budget was
|
|
20
|
+
* tight". The monorepo solves the same problem by routing boots to a lane
|
|
21
|
+
* with a 30s budget; a single-package project has one lane and nothing to
|
|
22
|
+
* contend with, so it states the budget instead.
|
|
23
|
+
*/
|
|
24
|
+
testTimeout: 30_000,
|
|
25
|
+
hookTimeout: 30_000,
|
|
7
26
|
},
|
|
8
27
|
});
|