create-nextly-app 0.0.2-alpha.3 → 0.0.2-alpha.31

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 (36) hide show
  1. package/dist/{chunk-AYJ2RKVJ.mjs → chunk-C5CWOU22.mjs} +1469 -1093
  2. package/dist/chunk-C5CWOU22.mjs.map +1 -0
  3. package/dist/cli.cjs +1497 -1123
  4. package/dist/cli.cjs.map +1 -1
  5. package/dist/cli.mjs +24 -27
  6. package/dist/cli.mjs.map +1 -1
  7. package/dist/index.cjs +1462 -1084
  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 +4 -4
  13. package/templates/base/next.config.ts +11 -17
  14. package/templates/base/src/app/admin/[[...params]]/page.tsx +0 -9
  15. package/templates/plugin/README.md +44 -0
  16. package/templates/plugin/dev/.env.example +8 -0
  17. package/templates/plugin/dev/instrumentation.ts +10 -0
  18. package/templates/plugin/dev/next.config.ts +30 -0
  19. package/templates/plugin/dev/nextly.config.ts +17 -0
  20. package/templates/plugin/dev/src/app/admin/[[...params]]/page.tsx +9 -0
  21. package/templates/plugin/dev/src/app/admin/api/[[...params]]/route.ts +19 -0
  22. package/templates/plugin/dev/src/app/layout.tsx +13 -0
  23. package/templates/plugin/dev/tsconfig.json +22 -0
  24. package/templates/plugin/eslint.config.mjs +8 -0
  25. package/templates/plugin/src/admin/SettingsPage.tsx +13 -0
  26. package/templates/plugin/src/admin/index.ts +21 -0
  27. package/templates/plugin/src/collections/example.ts +13 -0
  28. package/templates/plugin/src/index.ts +6 -0
  29. package/templates/plugin/src/plugin.test.ts +29 -0
  30. package/templates/plugin/src/plugin.ts +48 -0
  31. package/templates/plugin/src/types.ts +17 -0
  32. package/templates/plugin/template.json +14 -0
  33. package/templates/plugin/tsconfig.json +18 -0
  34. package/templates/plugin/tsup.config.ts +22 -0
  35. package/templates/plugin/vitest.config.ts +8 -0
  36. package/dist/chunk-AYJ2RKVJ.mjs.map +0 -1
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)
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)
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import { createRequire } from 'module';
2
- export { createNextly } from './chunk-AYJ2RKVJ.mjs';
2
+ export { createNextly } from './chunk-C5CWOU22.mjs';
3
3
 
4
4
  createRequire(import.meta.url);
5
5
  //# sourceMappingURL=index.mjs.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-nextly-app",
3
- "version": "0.0.2-alpha.3",
3
+ "version": "0.0.2-alpha.31",
4
4
  "description": "CLI to scaffold Nextly in your Next.js project",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -42,9 +42,9 @@
42
42
  "typescript": "^5.9.3",
43
43
  "vite-tsconfig-paths": "^5.1.4",
44
44
  "vitest": "^4.0.8",
45
- "@nextlyhq/eslint-config": "0.0.2-alpha.2",
46
- "@nextlyhq/telemetry": "0.0.2-alpha.2",
47
- "@nextlyhq/tsconfig": "0.0.2-alpha.2"
45
+ "@nextlyhq/eslint-config": "0.0.2-alpha.23",
46
+ "@nextlyhq/telemetry": "0.0.2-alpha.23",
47
+ "@nextlyhq/tsconfig": "0.0.2-alpha.23"
48
48
  },
49
49
  "engines": {
50
50
  "node": ">=20.0.0"
@@ -1,21 +1,15 @@
1
1
  import type { NextConfig } from "next";
2
2
 
3
- const nextConfig: NextConfig = {
4
- serverExternalPackages: [
5
- "nextly",
6
- "@nextlyhq/adapter-drizzle",
7
- "@nextlyhq/adapter-postgres",
8
- "@nextlyhq/adapter-mysql",
9
- "@nextlyhq/adapter-sqlite",
10
- "drizzle-orm",
11
- "drizzle-kit",
12
- "pg",
13
- "mysql2",
14
- "better-sqlite3",
15
- "bcryptjs",
16
- "sharp",
17
- "esbuild",
18
- ],
19
- };
3
+ /**
4
+ * Minimal base `next.config.ts` for bundled templates.
5
+ *
6
+ * This file is intentionally left minimal (no adapter whitelist). The
7
+ * scaffolder will generate or patch a database-specific `next.config.ts`
8
+ * that includes only the selected adapter and driver in
9
+ * `serverExternalPackages`. Keeping the base file minimal avoids
10
+ * advertising adapters the user didn't choose and prevents misleading
11
+ * generated projects.
12
+ */
13
+ const nextConfig: NextConfig = {};
20
14
 
21
15
  export default nextConfig;
@@ -1,15 +1,6 @@
1
1
  "use client";
2
2
 
3
- // Plugin-side admin imports. Every scaffold ships @nextlyhq/plugin-form-builder
4
- // as a dependency (see packages/create-nextly-app utils/template.ts). These
5
- // three lines register the plugin's custom admin field components and load its
6
- // CSS so the Forms collection's drag-and-drop field builder and Submissions
7
- // filter UI render correctly. Without them, Forms still appears in the sidebar
8
- // but the builder falls back to plain JSON/text inputs.
9
3
  import "@nextlyhq/admin/style.css";
10
- import "@nextlyhq/plugin-form-builder/admin";
11
- import "@nextlyhq/plugin-form-builder/styles/builder.css";
12
- import "@nextlyhq/plugin-form-builder/styles/submissions-filter.css";
13
4
  import { RootLayout, QueryProvider, ErrorBoundary } from "@nextlyhq/admin";
14
5
 
15
6
  export default function AdminPage() {
@@ -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,13 @@
1
+ /**
2
+ * Example plugin settings page, rendered at `/admin/plugins/<slug>` and inside
3
+ * a per-component error boundary (D53). Build your real settings UI here with
4
+ * `@nextlyhq/ui` components.
5
+ */
6
+ export function SettingsPage() {
7
+ return (
8
+ <div style={{ padding: 24 }}>
9
+ <h1>Plugin Settings</h1>
10
+ <p>Settings UI goes here. Edit src/admin/SettingsPage.tsx.</p>
11
+ </div>
12
+ );
13
+ }
@@ -0,0 +1,21 @@
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 { registerComponents, registerKnownPlugin } from "@nextlyhq/admin";
8
+
9
+ import { SettingsPage } from "./SettingsPage";
10
+
11
+ const SETTINGS_PATH = "{{pluginName}}/admin#SettingsPage";
12
+
13
+ // Eager: register on module load.
14
+ registerComponents({ [SETTINGS_PATH]: SettingsPage });
15
+
16
+ // Lazy fallback: the host can trigger registration on demand by package prefix.
17
+ registerKnownPlugin("{{pluginName}}", async () => {
18
+ registerComponents({ [SETTINGS_PATH]: SettingsPage });
19
+ });
20
+
21
+ 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,29 @@
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
+ const plugin = myPlugin();
10
+ t = await createTestNextly({
11
+ plugins: [plugin],
12
+ // Pass the plugin's collections so the harness creates their SQLite tables.
13
+ collections: plugin.contributes?.collections,
14
+ });
15
+ });
16
+
17
+ afterEach(async () => {
18
+ await t.destroy();
19
+ });
20
+
21
+ it("boots and exposes the plugin's example collection (real table)", async () => {
22
+ // Creating an entry proves the contributed collection got a real table and
23
+ // the plugin's lifecycle ran. Replace with assertions for your own behavior.
24
+ const created = await t.nextly.create("examples", { title: "First example" });
25
+ expect(created.item.id).toBeDefined();
26
+
27
+ const fetched = await t.nextly.findById("examples", created.item.id);
28
+ expect(fetched?.title).toBe("First example");
29
+ });
@@ -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,22 @@
1
+ import { defineConfig } from "tsup";
2
+
3
+ // Builds the publishable plugin. `dev/` is NOT an entry — it never ships.
4
+ // nextly / admin / react are peers, kept external.
5
+ export default defineConfig({
6
+ entry: ["src/index.ts", "src/admin/index.ts"],
7
+ format: ["esm"],
8
+ dts: true,
9
+ clean: true,
10
+ sourcemap: true,
11
+ treeshake: true,
12
+ external: [
13
+ "nextly",
14
+ "@nextlyhq/admin",
15
+ "@nextlyhq/plugin-sdk",
16
+ "react",
17
+ "react-dom",
18
+ ],
19
+ outExtension() {
20
+ return { js: ".mjs" };
21
+ },
22
+ });
@@ -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
+ });