create-nextly-app 0.0.2-alpha.6 → 0.0.2-alpha.60

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 (54) hide show
  1. package/dist/{chunk-6NVRVAZA.mjs → chunk-SEU6FVET.mjs} +2701 -1726
  2. package/dist/chunk-SEU6FVET.mjs.map +1 -0
  3. package/dist/cli.cjs +2803 -1831
  4. package/dist/cli.cjs.map +1 -1
  5. package/dist/cli.mjs +31 -35
  6. package/dist/cli.mjs.map +1 -1
  7. package/dist/index.cjs +2620 -1643
  8. package/dist/index.cjs.map +1 -1
  9. package/dist/index.d.cts +2 -1
  10. package/dist/index.d.ts +2 -1
  11. package/dist/index.mjs +1 -1
  12. package/package.json +11 -12
  13. package/templates/base/.env.example +11 -0
  14. package/templates/base/AGENTS.md.template +158 -0
  15. package/templates/base/CLAUDE.md.template +1 -0
  16. package/templates/base/gitignore +42 -0
  17. package/templates/base/next.config.ts +11 -17
  18. package/templates/base/src/app/admin/api/media/[[...path]]/route.ts +26 -0
  19. package/templates/base/src/app/globals.css +15 -2
  20. package/templates/base/src/app/layout.tsx +30 -15
  21. package/templates/base/tsconfig.json +8 -1
  22. package/templates/blank/src/app/globals.css +37 -1
  23. package/templates/blank/src/app/layout.tsx +26 -15
  24. package/templates/plugin/AGENTS.md.template +96 -0
  25. package/templates/plugin/CLAUDE.md.template +1 -0
  26. package/templates/plugin/README.md +44 -0
  27. package/templates/plugin/dev/.env.example +8 -0
  28. package/templates/plugin/dev/instrumentation.ts +10 -0
  29. package/templates/plugin/dev/next.config.ts +30 -0
  30. package/templates/plugin/dev/nextly.config.ts +17 -0
  31. package/templates/plugin/dev/src/app/admin/[[...params]]/page.tsx +9 -0
  32. package/templates/plugin/dev/src/app/admin/api/[[...params]]/route.ts +19 -0
  33. package/templates/plugin/dev/src/app/layout.tsx +13 -0
  34. package/templates/plugin/dev/tsconfig.json +22 -0
  35. package/templates/plugin/eslint.config.mjs +8 -0
  36. package/templates/plugin/gitignore +10 -0
  37. package/templates/plugin/src/admin/SettingsPage.tsx +57 -0
  38. package/templates/plugin/src/admin/index.ts +24 -0
  39. package/templates/plugin/src/collections/example.ts +13 -0
  40. package/templates/plugin/src/index.ts +6 -0
  41. package/templates/plugin/src/plugin.test.ts +28 -0
  42. package/templates/plugin/src/plugin.ts +48 -0
  43. package/templates/plugin/src/types.ts +17 -0
  44. package/templates/plugin/template.json +14 -0
  45. package/templates/plugin/tsconfig.json +18 -0
  46. package/templates/plugin/tsup.config.ts +24 -0
  47. package/templates/plugin/vitest.config.ts +8 -0
  48. package/dist/chunk-6NVRVAZA.mjs.map +0 -1
  49. package/templates/blank/src/access/README.md +0 -22
  50. package/templates/blank/src/collections/README.md +0 -22
  51. package/templates/blank/src/lib/README.md +0 -15
  52. package/templates/blank/src/singles/README.md +0 -19
  53. /package/templates/blank/src/{components → app/(frontend)/components}/README.md +0 -0
  54. /package/templates/blank/src/{components → app/(frontend)/components}/ThemeToggle.tsx +0 -0
@@ -0,0 +1,96 @@
1
+ <!-- nextly:managed:start -->
2
+ <!--
3
+ Everything between these markers is generated by create-nextly-app and may be
4
+ replaced when the project is upgraded. Write your own notes OUTSIDE the block:
5
+ anything above or below it is yours and is preserved.
6
+ -->
7
+
8
+ # Agent guide for {{pluginName}}
9
+
10
+ A [Nextly](https://nextlyhq.com) plugin. This is a publishable **library**, not
11
+ an app: `src/` is what ships, and `dev/` is a throwaway Nextly app that exists
12
+ only so you can exercise the plugin in a real admin.
13
+
14
+ ## Where things are
15
+
16
+ | path | what it is |
17
+ | ----------------------- | ---------------------------------------------------------------- |
18
+ | `src/index.ts` | the public entry — React-free, so it stays importable on the server |
19
+ | `src/admin/index.ts` | the `./admin` subpath, where React components are registered |
20
+ | `src/plugin.ts` | the plugin factory: what it contributes and what it does on init |
21
+ | `src/collections/` | collections the plugin contributes to its host |
22
+ | `dev/` | the embedded playground: a minimal Nextly app on SQLite |
23
+ | `dist/` | the build output, and the only thing `npm publish` ships |
24
+
25
+ ## Commands
26
+
27
+ ```bash
28
+ {{runCommand}} dev # the embedded playground → http://localhost:3000/admin
29
+ {{runCommand}} test # vitest, against a real Nextly on in-memory SQLite
30
+ {{runCommand}} build # tsup → dist/
31
+ {{runCommand}} check-types # tsc --noEmit
32
+ {{runCommand}} lint # eslint
33
+ ```
34
+
35
+ `{{runCommand}} dev` runs `next dev dev` — Next.js with `dev/` as the project root.
36
+ Editing anything under `src/` reloads the playground, so the loop is the same as
37
+ working on an app.
38
+
39
+ ## Things that surprise people
40
+
41
+ - **Plugin APIs come from `@nextlyhq/plugin-sdk`; schema APIs come from
42
+ `nextly/config`.** The SDK is the stability boundary for everything a plugin
43
+ does AS a plugin: `definePlugin`, `registerComponents` and
44
+ `registerKnownPlugin` from `@nextlyhq/plugin-sdk/admin`, `createTestNextly`
45
+ from `@nextlyhq/plugin-sdk/testing`, field factories, `pluginField`,
46
+ `NextlyError`. Collections and singles are defined the same way an app defines
47
+ them, with `defineCollection` from `nextly/config` — which is what
48
+ `src/collections/example.ts` does.
49
+
50
+ **What is never supported is importing `@nextlyhq/admin` directly.** It carries
51
+ no compatibility promise to plugin authors, so an import that compiles today
52
+ breaks on a patch release. If you need something from it that the SDK does not
53
+ re-export, that is worth raising rather than reaching past the boundary.
54
+
55
+ - **The root export must stay React-free.** It is imported by the host's server
56
+ config, so a React import at `src/index.ts` breaks apps that never render your
57
+ admin UI. Components belong on the `./admin` subpath, which is where the host
58
+ looks for them.
59
+ - **Resolve your own slugs through `ctx.self`.** A host can rename a collection
60
+ your plugin contributes; `ctx.self.collections.examples` follows the rename,
61
+ a hardcoded `"examples"` does not.
62
+ - **`dev/` is never published.** `files` is `["dist"]`, so the playground can
63
+ depend on anything it likes without adding weight for consumers.
64
+ - **Peers, not dependencies.** `nextly`, `@nextlyhq/admin`,
65
+ `@nextlyhq/plugin-sdk` and React are peer dependencies — the host supplies
66
+ them, and bundling a second copy is what causes the "two Reacts" class of
67
+ failure.
68
+
69
+ ## Testing
70
+
71
+ `createTestNextly` from `@nextlyhq/plugin-sdk/testing` boots a real Nextly on
72
+ in-memory SQLite with your plugin registered, so a test exercises the same
73
+ lifecycle a host would run:
74
+
75
+ ```ts
76
+ const t = await createTestNextly({ plugins: [myPlugin()] });
77
+ ```
78
+
79
+ **Pass `plugins` alone.** Registration auto-syncs the tables for contributed
80
+ collections, so passing the same collections again as `collections` registers
81
+ those slugs a second time as code-owned entities and boot fails with
82
+ `NEXTLY_SCHEMA_SLUG_COLLISION`.
83
+
84
+ Assert on behaviour through `t.nextly` rather than on the plugin object: what a
85
+ host observes is the contract, and the object shape is not.
86
+
87
+ ## Conventions worth keeping
88
+
89
+ - Version `nextly` compatibility in the factory's `nextly` range, and widen it
90
+ deliberately rather than by default.
91
+ - Clean up in `destroy()`. Event subscriptions are dropped for you on registry
92
+ reset; timers, watchers and connections are not.
93
+ - Keep `contributes` declarative. Work belongs in `init()`, where the context is
94
+ available and failures are attributable to your plugin.
95
+
96
+ <!-- nextly:managed:end -->
@@ -0,0 +1 @@
1
+ @AGENTS.md
@@ -0,0 +1,44 @@
1
+ # {{pluginName}}
2
+
3
+ A [Nextly](https://nextlyhq.com) plugin.
4
+
5
+ ## Develop
6
+
7
+ ```bash
8
+ pnpm install
9
+ pnpm dev # runs the embedded /dev playground → open http://localhost:3000/admin
10
+ ```
11
+
12
+ Your plugin lives in `src/`. The `dev/` folder is a minimal Nextly app on SQLite
13
+ that registers this plugin so you can exercise it in a real admin with hot-reload.
14
+ Editing files under `src/` reloads the playground. **`dev/` is never published.**
15
+
16
+ ## Test
17
+
18
+ ```bash
19
+ pnpm test # boots a real Nextly on in-memory SQLite via createTestNextly
20
+ ```
21
+
22
+ ## Build & publish
23
+
24
+ ```bash
25
+ pnpm build # tsup → dist/
26
+ npm publish
27
+ ```
28
+
29
+ Only `dist/` ships (see `files` in `package.json`). The `nextly-plugin` keyword
30
+ makes this package discoverable.
31
+
32
+ ## Use in an app
33
+
34
+ ```ts
35
+ // nextly.config.ts
36
+ import { defineConfig } from "nextly";
37
+ import { myPlugin } from "{{pluginName}}";
38
+
39
+ export default defineConfig({
40
+ plugins: [myPlugin()],
41
+ });
42
+ ```
43
+
44
+ See the [Nextly plugin author guide](https://nextlyhq.com/docs/plugins/author-guide).
@@ -0,0 +1,8 @@
1
+ # Dev playground env. Copy to dev/.env and run `pnpm dev`.
2
+ DB_DIALECT=sqlite
3
+ DATABASE_URL=file:./nextly-dev.db
4
+
5
+ # Any non-empty secret works for local dev (sign/verify session cookies).
6
+ NEXTLY_SECRET=dev-secret-change-me
7
+
8
+ NEXT_PUBLIC_SITE_URL=http://localhost:3000
@@ -0,0 +1,10 @@
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.
4
+ export async function register(): Promise<void> {
5
+ if (process.env.NEXT_RUNTIME !== "nodejs") return;
6
+
7
+ const { createRegister } = await import("nextly");
8
+ const { default: config } = await import("./nextly.config");
9
+ await createRegister(config)();
10
+ }
@@ -0,0 +1,30 @@
1
+ import type { NextConfig } from "next";
2
+
3
+ // Dev playground for the plugin. The plugin's own source (../src) is aliased and
4
+ // transpiled so editing it hot-reloads. `nextly` + the admin come from
5
+ // node_modules (real deps). Node-only / dynamically-imported packages stay
6
+ // external (Turbopack can't bundle them).
7
+ const nextConfig: NextConfig = {
8
+ typescript: { ignoreBuildErrors: true },
9
+ // Transpile the aliased plugin source so HMR fires on src/ edits.
10
+ transpilePackages: ["{{pluginName}}"],
11
+ serverExternalPackages: [
12
+ "better-sqlite3",
13
+ "drizzle-orm",
14
+ "drizzle-kit",
15
+ "esbuild",
16
+ "bundle-require",
17
+ "sharp",
18
+ "@nextlyhq/adapter-drizzle",
19
+ "@nextlyhq/adapter-sqlite",
20
+ ],
21
+ turbopack: {
22
+ resolveAlias: {
23
+ // Map the package name to its source so HMR works without a build step.
24
+ "{{pluginName}}": ["./../src/index.ts"],
25
+ "{{pluginName}}/admin": ["./../src/admin/index.ts"],
26
+ },
27
+ },
28
+ };
29
+
30
+ export default nextConfig;
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Dev playground config — a minimal Nextly app that registers THIS plugin so you
3
+ * can exercise it in a real admin. SQLite, dev auto-login. Never published.
4
+ */
5
+ import { defineConfig } from "nextly/config";
6
+ import { myPlugin } from "{{pluginName}}";
7
+
8
+ export default defineConfig({
9
+ admin: {
10
+ // Land on /admin already logged in. Hard-blocked in production by Nextly.
11
+ devAutoLogin: {
12
+ email: "dev@nextly.local",
13
+ password: "DevPassword123!",
14
+ },
15
+ },
16
+ plugins: [myPlugin()],
17
+ });
@@ -0,0 +1,9 @@
1
+ "use client";
2
+
3
+ import "@nextlyhq/admin/style.css";
4
+ import { RootLayout } from "@nextlyhq/admin";
5
+
6
+ // Mounts the Nextly admin shell. Your plugin's menu/pages/settings appear here.
7
+ export default function AdminPage() {
8
+ return <RootLayout />;
9
+ }
@@ -0,0 +1,19 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any -- framework adapter passes through Next.js App Router context */
2
+ /**
3
+ * Admin API catch-all. Routes every /admin/api/* request (incl. your plugin's
4
+ * /api/plugins/<name>/… routes) through the handler built from nextly.config.ts.
5
+ */
6
+ import { createDynamicHandlers } from "nextly/runtime";
7
+
8
+ import nextlyConfig from "../../../../../nextly.config";
9
+
10
+ const handlers = createDynamicHandlers({ config: nextlyConfig });
11
+
12
+ export const GET = (req: Request, context: any) => handlers.GET(req, context);
13
+ export const POST = (req: Request, context: any) => handlers.POST(req, context);
14
+ export const PUT = (req: Request, context: any) => handlers.PUT(req, context);
15
+ export const PATCH = (req: Request, context: any) =>
16
+ handlers.PATCH(req, context);
17
+ export const DELETE = (req: Request, context: any) =>
18
+ handlers.DELETE(req, context);
19
+ export const OPTIONS = (req: Request) => handlers.OPTIONS(req);
@@ -0,0 +1,13 @@
1
+ import type { ReactNode } from "react";
2
+
3
+ export const metadata = {
4
+ title: "{{pluginName}} — dev playground",
5
+ };
6
+
7
+ export default function RootLayout({ children }: { children: ReactNode }) {
8
+ return (
9
+ <html lang="en">
10
+ <body>{children}</body>
11
+ </html>
12
+ );
13
+ }
@@ -0,0 +1,22 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
5
+ "module": "ESNext",
6
+ "moduleResolution": "Bundler",
7
+ "jsx": "preserve",
8
+ "strict": true,
9
+ "noEmit": true,
10
+ "esModuleInterop": true,
11
+ "skipLibCheck": true,
12
+ "resolveJsonModule": true,
13
+ "isolatedModules": true,
14
+ "plugins": [{ "name": "next" }],
15
+ "paths": {
16
+ "{{pluginName}}": ["../src/index.ts"],
17
+ "{{pluginName}}/admin": ["../src/admin/index.ts"]
18
+ }
19
+ },
20
+ "include": ["**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
21
+ "exclude": ["node_modules"]
22
+ }
@@ -0,0 +1,8 @@
1
+ import js from "@eslint/js";
2
+ import tseslint from "typescript-eslint";
3
+
4
+ export default tseslint.config(
5
+ { ignores: ["dist", "dev/.next", "node_modules"] },
6
+ js.configs.recommended,
7
+ ...tseslint.configs.recommended
8
+ );
@@ -0,0 +1,10 @@
1
+ node_modules
2
+ dist
3
+ *.log
4
+
5
+ # Embedded dev playground — never published, never committed build output
6
+ dev/.next
7
+ dev/*.db
8
+ dev/*.db-journal
9
+ .env
10
+ .env.local
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Example plugin settings page, rendered at `/admin/plugins/<slug>` and wrapped in an
3
+ * error boundary, so a mistake here cannot take the admin down with it.
4
+ *
5
+ * Every class here is a design token utility, so this page follows the admin's
6
+ * theme and its light/dark modes with no styling work of its own. Build your
7
+ * real settings UI the same way.
8
+ *
9
+ * Third-party plugins are not part of the admin's Tailwind `@source` scan, so
10
+ * only the curated utilities in `plugin-safelist.css` are guaranteed to be
11
+ * compiled — layout, spacing, type, and the token-mapped colours used below.
12
+ * Anything outside that set belongs in the stylesheet your plugin declares as
13
+ * `adminStyles`, written against the `--nx-*` custom properties.
14
+ */
15
+ export function SettingsPage() {
16
+ return (
17
+ <div className="w-full max-w-2xl space-y-6 p-6">
18
+ <header className="space-y-1">
19
+ <h1 className="text-xl font-semibold tracking-tight text-foreground">
20
+ Plugin Settings
21
+ </h1>
22
+ <p className="text-sm text-muted-foreground">
23
+ Settings UI goes here. Edit{" "}
24
+ <span className="font-mono text-xs">src/admin/SettingsPage.tsx</span>.
25
+ </p>
26
+ </header>
27
+
28
+ <section className="space-y-4 rounded-lg border border-border bg-card p-6">
29
+ <div className="space-y-1">
30
+ <h2 className="text-sm font-semibold text-foreground">
31
+ Where to put your fields
32
+ </h2>
33
+ <p className="text-sm leading-relaxed text-muted-foreground">
34
+ Replace this card with your own form. Group related settings under a
35
+ heading, and keep one save action for the page rather than one per
36
+ group.
37
+ </p>
38
+ </div>
39
+
40
+ <ul className="space-y-2 text-sm text-muted-foreground">
41
+ <li>
42
+ Colours come from tokens —{" "}
43
+ <span className="font-mono text-xs">bg-card</span>,{" "}
44
+ <span className="font-mono text-xs">text-muted-foreground</span>,{" "}
45
+ <span className="font-mono text-xs">border-border</span> — never
46
+ from a fixed palette shade, which would keep its light-mode
47
+ appearance in dark mode.
48
+ </li>
49
+ <li>
50
+ Spacing and radius come from the same scales the rest of the admin
51
+ uses, so your page stays aligned with it when those move.
52
+ </li>
53
+ </ul>
54
+ </section>
55
+ </div>
56
+ );
57
+ }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Admin component registration (D19/D60). Importing this module registers the
3
+ * React components referenced by `contributes.admin` string paths so the host
4
+ * admin can resolve them. The host's generated import map imports this for you;
5
+ * the eager + lazy registration below is the fallback.
6
+ */
7
+ import {
8
+ registerComponents,
9
+ registerKnownPlugin,
10
+ } from "@nextlyhq/plugin-sdk/admin";
11
+
12
+ import { SettingsPage } from "./SettingsPage";
13
+
14
+ const SETTINGS_PATH = "{{pluginName}}/admin#SettingsPage";
15
+
16
+ // Eager: register on module load.
17
+ registerComponents({ [SETTINGS_PATH]: SettingsPage });
18
+
19
+ // Lazy fallback: the host can trigger registration on demand by package prefix.
20
+ registerKnownPlugin("{{pluginName}}", async () => {
21
+ registerComponents({ [SETTINGS_PATH]: SettingsPage });
22
+ });
23
+
24
+ export { SettingsPage };
@@ -0,0 +1,13 @@
1
+ import { defineCollection, text, textarea } from "nextly/config";
2
+
3
+ /**
4
+ * An example plugin-owned collection. Replace or remove it with your own.
5
+ * Plugin collections are merged into the host schema automatically (D3/D12)
6
+ * and get a real table on `nextly migrate` / boot.
7
+ */
8
+ export const Examples = defineCollection({
9
+ slug: "examples",
10
+ labels: { singular: "Example", plural: "Examples" },
11
+ fields: [text({ name: "title", required: true }), textarea({ name: "body" })],
12
+ admin: { useAsTitle: "title" },
13
+ });
@@ -0,0 +1,6 @@
1
+ /**
2
+ * {{pluginName}} — public entry. The default `.` export is React-free
3
+ * (Node-safe). Admin React components live on the `./admin` subpath.
4
+ */
5
+ export { myPlugin } from "./plugin";
6
+ export type { MyPluginOptions } from "./types";
@@ -0,0 +1,28 @@
1
+ import { createTestNextly } from "@nextlyhq/plugin-sdk/testing";
2
+ import { afterEach, beforeEach, expect, it } from "vitest";
3
+
4
+ import { myPlugin } from "./index";
5
+
6
+ let t: Awaited<ReturnType<typeof createTestNextly>>;
7
+
8
+ beforeEach(async () => {
9
+ // `plugins` alone. Runtime auto-sync creates the tables for contributed
10
+ // collections during registration, so passing them again as `collections`
11
+ // registers the same slugs as code-owned entities and boot fails with
12
+ // NEXTLY_SCHEMA_SLUG_COLLISION.
13
+ t = await createTestNextly({ plugins: [myPlugin()] });
14
+ });
15
+
16
+ afterEach(async () => {
17
+ await t.destroy();
18
+ });
19
+
20
+ it("boots and exposes the plugin's example collection (real table)", async () => {
21
+ // Creating an entry proves the contributed collection got a real table and
22
+ // the plugin's lifecycle ran. Replace with assertions for your own behavior.
23
+ const created = await t.nextly.create("examples", { title: "First example" });
24
+ expect(created.item.id).toBeDefined();
25
+
26
+ const fetched = await t.nextly.findById("examples", created.item.id);
27
+ expect(fetched?.title).toBe("First example");
28
+ });
@@ -0,0 +1,48 @@
1
+ import { definePlugin } from "@nextlyhq/plugin-sdk";
2
+
3
+ import { Examples } from "./collections/example";
4
+ import type { MyPluginOptions } from "./types";
5
+
6
+ /**
7
+ * Your plugin factory. Call it in a host app's `defineConfig({ plugins: [...] })`.
8
+ *
9
+ * @example
10
+ * ```ts
11
+ * import { myPlugin } from "{{pluginName}}";
12
+ * export default defineConfig({ plugins: [myPlugin({ greeting: "Hi" })] });
13
+ * ```
14
+ */
15
+ export const myPlugin = (opts: MyPluginOptions = {}) =>
16
+ definePlugin({
17
+ name: "{{pluginName}}",
18
+ version: "0.1.0",
19
+ nextly: "{{nextlyRange}}",
20
+ enabled: opts.enabled,
21
+ contributes: {
22
+ collections: [Examples],
23
+ permissions: [
24
+ { action: "manage", resource: "examples", label: "Manage Examples" },
25
+ ],
26
+ admin: {
27
+ menu: [
28
+ {
29
+ label: "Examples",
30
+ to: "/admin/collections/examples",
31
+ icon: "Sparkles",
32
+ },
33
+ ],
34
+ settings: { component: "{{pluginName}}/admin#SettingsPage" },
35
+ },
36
+ },
37
+ init(ctx) {
38
+ const greeting = opts.greeting ?? "Hello";
39
+ // Resolve your own slug via ctx.self so this keeps working if the host
40
+ // renames the collection (D54). React to the post-commit create event.
41
+ ctx.events.on(`collection.${ctx.self.collections.examples}.created`, () =>
42
+ ctx.logger.info(`${greeting} from {{pluginName}} — example created`)
43
+ );
44
+ },
45
+ destroy() {
46
+ // Clean up here (subscriptions are auto-dropped on registry reset).
47
+ },
48
+ });
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Options accepted by your plugin factory. These are JSDoc-documented so editors
3
+ * surface them at the call site (D44).
4
+ */
5
+ export interface MyPluginOptions {
6
+ /**
7
+ * Greeting prefix used by the example collection's `afterCreate` hook.
8
+ * @default "Hello"
9
+ */
10
+ greeting?: string;
11
+
12
+ /**
13
+ * Disable the plugin's behavior (init/hooks/events/routes/admin) while still
14
+ * applying its schema contributions. Default `true`.
15
+ */
16
+ enabled?: boolean;
17
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "name": "plugin",
3
+ "label": "Plugin",
4
+ "description": "Build a reusable Nextly plugin (publishable npm package)",
5
+ "hint": "Plugin package + embedded /dev playground; no schema approach",
6
+ "approaches": [],
7
+ "defaultApproach": null,
8
+ "collections": [],
9
+ "singles": [],
10
+ "hasDemoData": false,
11
+ "hasFrontendPages": false,
12
+ "recommendedDatabase": "sqlite",
13
+ "release": "alpha"
14
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "Bundler",
6
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
7
+ "jsx": "react-jsx",
8
+ "strict": true,
9
+ "noEmit": true,
10
+ "esModuleInterop": true,
11
+ "skipLibCheck": true,
12
+ "resolveJsonModule": true,
13
+ "isolatedModules": true,
14
+ "verbatimModuleSyntax": false
15
+ },
16
+ "include": ["src"],
17
+ "exclude": ["node_modules", "dist", "dev"]
18
+ }
@@ -0,0 +1,24 @@
1
+ import { defineConfig } from "tsup";
2
+
3
+ // Builds the publishable plugin. `dev/` is NOT an entry — it never ships.
4
+ // nextly / admin / sdk / ui / react are peers, kept external: bundling one
5
+ // would ship a second copy of code the host already provides.
6
+ export default defineConfig({
7
+ entry: ["src/index.ts", "src/admin/index.ts"],
8
+ format: ["esm"],
9
+ dts: true,
10
+ clean: true,
11
+ sourcemap: true,
12
+ treeshake: true,
13
+ external: [
14
+ "nextly",
15
+ "@nextlyhq/admin",
16
+ "@nextlyhq/plugin-sdk",
17
+ "@nextlyhq/ui",
18
+ "react",
19
+ "react-dom",
20
+ ],
21
+ outExtension() {
22
+ return { js: ".mjs" };
23
+ },
24
+ });
@@ -0,0 +1,8 @@
1
+ import { defineConfig } from "vitest/config";
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ environment: "node",
6
+ include: ["src/**/*.test.ts"],
7
+ },
8
+ });