create-nextly-app 0.0.2-alpha.73 → 0.0.2-alpha.75

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/dist/index.cjs CHANGED
@@ -1,3 +1,3 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_create_nextly = require("./create-nextly-Q62KZJib.cjs");
2
+ const require_create_nextly = require("./create-nextly-DdIL2e9E.cjs");
3
3
  exports.createNextly = require_create_nextly.createNextly;
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- export { t as createNextly } from "./create-nextly-CuWn_ITx.mjs";
1
+ export { t as createNextly } from "./create-nextly-N_COehCu.mjs";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-nextly-app",
3
- "version": "0.0.2-alpha.73",
3
+ "version": "0.0.2-alpha.75",
4
4
  "description": "CLI to scaffold Nextly in your Next.js project",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -37,10 +37,10 @@
37
37
  },
38
38
  "devDependencies": {
39
39
  "@babel/types": "^7.29.7",
40
- "@nextlyhq/eslint-plugin": "0.0.2-alpha.73",
41
- "@nextlyhq/module-specifiers": "0.0.2-alpha.73",
42
- "@nextlyhq/telemetry": "0.0.2-alpha.73",
43
- "@nextlyhq/tsconfig": "0.0.2-alpha.73",
40
+ "@nextlyhq/eslint-plugin": "0.0.2-alpha.75",
41
+ "@nextlyhq/module-specifiers": "0.0.2-alpha.75",
42
+ "@nextlyhq/telemetry": "0.0.2-alpha.75",
43
+ "@nextlyhq/tsconfig": "0.0.2-alpha.75",
44
44
  "@testing-library/react": "^16.3.0",
45
45
  "@types/fs-extra": "^11.0.4",
46
46
  "@types/node": "^22.20.5",
@@ -7,7 +7,7 @@ description: >-
7
7
  code, and extra fields on users. Also use when asked to add login or
8
8
  sign-up to a Nextly site, so the answer matches what Nextly provides.
9
9
  metadata:
10
- nextly-version: 0.0.2-alpha.73
10
+ nextly-version: 0.0.2-alpha.75
11
11
  ---
12
12
 
13
13
  # Authentication and access in Nextly
@@ -9,7 +9,7 @@ description: >-
9
9
  not appear in the admin, a field change does not reach the database, or a
10
10
  page reading content fails to build.
11
11
  metadata:
12
- nextly-version: 0.0.2-alpha.73
12
+ nextly-version: 0.0.2-alpha.75
13
13
  ---
14
14
 
15
15
  # Define a collection in Nextly
@@ -8,7 +8,7 @@ description: >-
8
8
  Also use when a deployed Nextly app has missing tables, refuses to start,
9
9
  or loses uploaded files.
10
10
  metadata:
11
- nextly-version: 0.0.2-alpha.73
11
+ nextly-version: 0.0.2-alpha.75
12
12
  ---
13
13
 
14
14
  # Deploy a Nextly project
@@ -8,7 +8,7 @@ description: >-
8
8
  Nextly install shows no collections, fails to load nextly.config.ts, or its
9
9
  admin panel misbehaves after setup.
10
10
  metadata:
11
- nextly-version: 0.0.2-alpha.73
11
+ nextly-version: 0.0.2-alpha.75
12
12
  ---
13
13
 
14
14
  # Install Nextly into an existing Next.js app
@@ -1,11 +1,43 @@
1
1
  import { connection } from "next/server";
2
2
  import { Suspense } from "react";
3
- import { getBrandingCss } from "nextly/config";
3
+ import {
4
+ ADMIN_BRANDING_ELEMENT_ID,
5
+ adminBrandingJson,
6
+ adminMetadataFor,
7
+ getBrandingCss,
8
+ } from "nextly/config";
4
9
 
5
10
  import config from "../../../../nextly.config";
11
+ import { registeredComponentPaths } from "../../../types/generated/plugin-admin-components.generated";
6
12
 
7
13
  const brandingCss = getBrandingCss(config.admin?.branding);
14
+ // The configured logo, name and favicon, read by the admin before any request
15
+ // answers, so they are right from the first paint.
16
+ const brandingJson = adminBrandingJson(config.admin?.branding);
8
17
 
18
+ /**
19
+ * The admin is never a page for search results: `noindex, nofollow` in the
20
+ * head of the first HTML the server sends, whatever plugins the site runs,
21
+ * and the configured favicon's link beside it.
22
+ */
23
+ export const metadata = adminMetadataFor(config.admin?.branding);
24
+
25
+ /**
26
+ * `/admin` itself is built ahead, so it is served from the build's files. The
27
+ * admin's other paths are drawn by the same page in the browser, and are
28
+ * served the same prebuilt shell.
29
+ */
30
+ export function generateStaticParams(): { params: string[] }[] {
31
+ return [{ params: [] }];
32
+ }
33
+
34
+ /**
35
+ * Prebuilt in production: the same page for every visitor, built once, with
36
+ * no request-time work and nothing read from the database. Everything about
37
+ * the person comes from the admin's own requests once it loads, so a CDN or
38
+ * the browser may keep the page, and it is served while the server is still
39
+ * starting.
40
+ */
9
41
  export default function AdminLayout({
10
42
  children,
11
43
  }: {
@@ -16,19 +48,39 @@ export default function AdminLayout({
16
48
  {brandingCss && (
17
49
  <style dangerouslySetInnerHTML={{ __html: brandingCss }} />
18
50
  )}
19
- <Suspense fallback={null}>
20
- <ForThisRequest>{children}</ForThisRequest>
21
- </Suspense>
51
+ {brandingJson && (
52
+ <script
53
+ type="application/json"
54
+ id={ADMIN_BRANDING_ELEMENT_ID}
55
+ dangerouslySetInnerHTML={{ __html: brandingJson }}
56
+ />
57
+ )}
58
+ {process.env.NODE_ENV === "development" ? (
59
+ <Suspense fallback={null}>
60
+ <ComponentMapKeptCurrent>{children}</ComponentMapKeptCurrent>
61
+ </Suspense>
62
+ ) : (
63
+ children
64
+ )}
22
65
  </>
23
66
  );
24
67
  }
25
68
 
26
69
  /**
27
- * The admin renders for each request, never from a cache another user's
28
- * request filled. Beneath the layout's Suspense boundary, so under Cache
29
- * Components the shell above it still prerenders.
70
+ * Development only: before any admin page, the generated component map is
71
+ * written again when it falls behind the config. A production build serves
72
+ * the map it was built with, so it drops this, and the page with it is
73
+ * prebuilt. Beneath a Suspense boundary, as request work outside one fails a
74
+ * Cache Components build, in development too. The check is loaded here, so a
75
+ * production page never loads the runtime it lives in.
30
76
  */
31
- async function ForThisRequest({ children }: { children: React.ReactNode }) {
77
+ async function ComponentMapKeptCurrent({
78
+ children,
79
+ }: {
80
+ children: React.ReactNode;
81
+ }) {
32
82
  await connection();
83
+ const { checkAdminComponents } = await import("nextly/runtime");
84
+ await checkAdminComponents({ config, registeredComponentPaths });
33
85
  return children;
34
86
  }
@@ -3,6 +3,11 @@ import "@fontsource-variable/geist";
3
3
  import "@fontsource-variable/geist-mono";
4
4
  import "./globals.css";
5
5
  import { SITE_URL } from "@/lib/site-url";
6
+ // Registers the app's "use cache" read function, and how Nextly starts when a
7
+ // read finds it not started, for every route before any of them reads,
8
+ // during `next build` too: under Cache Components Nextly's cached reads go
9
+ // through that function, and `<SiteCode>` refuses to read without it.
10
+ import "@/nextly/cached-read";
6
11
 
7
12
  /**
8
13
  * The faces are imported as STYLESHEETS from packages in `node_modules`, rather
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Nextly's cached reads under Cache Components.
3
+ *
4
+ * Next.js caches non-fetch reads with `"use cache"` and `cacheTag` once Cache
5
+ * Components is on, and the directive works only in code Next.js compiles, so
6
+ * Nextly reads through this function of the app's own. It receives the read's
7
+ * key, its tags and its lifetime, and runs the reader Nextly registered for
8
+ * that key. The root layout imports this file, so it is registered on every
9
+ * route before anything is read: `<SiteCode>` refuses to read without it.
10
+ *
11
+ * The same import also tells Nextly how to start with the app's config when a
12
+ * read finds it not started, so the root layout's import both registers the
13
+ * cached read and covers a read made before any page starts Nextly, during
14
+ * `next build` too.
15
+ *
16
+ * It works with `cacheComponents: true` and Nextly's cache handlers in
17
+ * `next.config.ts`, as this app ships:
18
+ * https://nextlyhq.com/docs/guides/isr-caching#cache-components
19
+ */
20
+ import { cacheLife, cacheTag } from "next/cache";
21
+ import { getNextly, registerNextlyStart } from "nextly";
22
+ import { registerCachedRead, runRegisteredRead } from "nextly/runtime";
23
+
24
+ import config from "@nextly-config";
25
+
26
+ export async function cachedRead(
27
+ key: string,
28
+ tags: string[],
29
+ revalidate: number | false
30
+ ): Promise<unknown> {
31
+ "use cache";
32
+ cacheTag(...tags);
33
+ // `revalidate: false` is tag-only, as everywhere in Nextly: only a write
34
+ // expires the entry, never time. Next.js's "max" profile revalidates monthly.
35
+ if (revalidate === false) {
36
+ cacheLife({ revalidate: Infinity, expire: Infinity });
37
+ } else {
38
+ // Expired at twice the revalidate, and never within 5 minutes: Next.js
39
+ // leaves an entry that expires sooner out of a static prerender.
40
+ cacheLife({ revalidate, expire: Math.max(revalidate * 2, 300) });
41
+ }
42
+ return runRegisteredRead(key);
43
+ }
44
+
45
+ registerCachedRead(cachedRead, process.env.__NEXT_CACHE_COMPONENTS);
46
+
47
+ // Tells Nextly how this app starts it, for a read that finds it not started,
48
+ // such as the root layout's `<SiteCode>` on `/_not-found` while `next build`
49
+ // prerenders, where nothing else starts it. Nothing starts here: a page that
50
+ // calls `getNextly` first is the start that counts. An app that passes
51
+ // `getNextly` more than its config passes the same options here.
52
+ registerNextlyStart(() => getNextly({ config }));
@@ -3,6 +3,7 @@
3
3
  * string (pages, settings, views, slots, field-type editors) so they load
4
4
  * without manual host imports. */
5
5
 
6
+ import { registerComponentMap } from "@nextlyhq/admin";
6
7
  import { registeredComponentPaths as listed } from "./plugin-admin-components.generated";
7
8
 
8
9
  const registered = {};
@@ -29,4 +30,5 @@ if (
29
30
  );
30
31
  }
31
32
 
33
+ registerComponentMap(registered);
32
34
  export const registeredComponentPaths = listed;
@@ -4,6 +4,11 @@ import "@fontsource-variable/jetbrains-mono";
4
4
 
5
5
  import "./globals.css";
6
6
  import { SITE_URL } from "@/lib/site-url";
7
+ // Registers the app's "use cache" read function, and how Nextly starts when a
8
+ // read finds it not started, for every route before any of them reads,
9
+ // during `next build` too: under Cache Components Nextly's cached reads go
10
+ // through that function, and `<SiteCode>` refuses to read without it.
11
+ import "@/nextly/cached-read";
7
12
 
8
13
  /**
9
14
  * The faces are imported as STYLESHEETS from packages in `node_modules`, rather
@@ -26,8 +26,11 @@ export async function register(): Promise<void> {
26
26
  // Next.js writes its Cache Components setting into this file as it builds
27
27
  // it. Nextly, loaded from node_modules, cannot read that for itself. With
28
28
  // it off, Nextly warns at start; with it on, a cached read that finds no
29
- // "use cache" read function says so.
29
+ // "use cache" read function says so. The same holds for the build's newest
30
+ // migration, which withNextly sets: a production start compares it with
31
+ // the database's schema version and warns when the database is behind.
30
32
  await createRegister(config, {
31
33
  cacheComponents: process.env.__NEXT_CACHE_COMPONENTS,
34
+ buildSchemaVersion: process.env.NEXTLY_BUILD_SCHEMA_VERSION,
32
35
  })();
33
36
  }
@@ -41,8 +41,8 @@ working on an app.
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
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`
44
+ `NextlyError` from `@nextlyhq/plugin-sdk`; `registerComponents` from
45
+ `@nextlyhq/plugin-sdk/admin`; `createTestNextly`
46
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.
@@ -1,5 +1,11 @@
1
1
  import type { ReactNode } from "react";
2
2
 
3
+ // Registers the dev app's "use cache" read function, and how Nextly starts
4
+ // when a read finds it not started, for every route before any of them
5
+ // reads, during `next build` too: under Cache Components Nextly's cached
6
+ // reads go through that function, and `<SiteCode>` refuses to read without it.
7
+ import "../nextly/cached-read";
8
+
3
9
  export const metadata = {
4
10
  title: "{{pluginName}} — dev playground",
5
11
  };
@@ -10,9 +10,12 @@ export async function register(): Promise<void> {
10
10
  // Next.js writes its Cache Components setting into this file as it builds
11
11
  // it. Nextly, loaded from node_modules, cannot read that for itself. With
12
12
  // it off, Nextly warns at start; with it on, a cached read that finds no
13
- // "use cache" read function says so.
13
+ // "use cache" read function says so. The same holds for the build's newest
14
+ // migration, which withNextly sets: a production start compares it with
15
+ // the database's schema version and warns when the database is behind.
14
16
  await createRegister(config, {
15
17
  cacheComponents: process.env.__NEXT_CACHE_COMPONENTS,
18
+ buildSchemaVersion: process.env.NEXTLY_BUILD_SCHEMA_VERSION,
16
19
  })();
17
20
 
18
21
  // Create the user admin.devAutoLogin signs in as (see seed-dev-user.ts).
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Nextly's cached reads under Cache Components, for the plugin's dev app.
3
+ *
4
+ * Next.js caches non-fetch reads with `"use cache"` and `cacheTag` once Cache
5
+ * Components is on, and the directive works only in code Next.js compiles, so
6
+ * Nextly reads through this function of the app's own. It receives the read's
7
+ * key, its tags and its lifetime, and runs the reader Nextly registered for
8
+ * that key. The root layout imports this file, so it is registered on every
9
+ * route before anything is read, as in an app `create-nextly-app` makes.
10
+ *
11
+ * The same import also tells Nextly how to start with the app's config when a
12
+ * read finds it not started, so the root layout's import both registers the
13
+ * cached read and covers a read made before any page starts Nextly, during
14
+ * `next build` too.
15
+ *
16
+ * It works with `cacheComponents: true` and Nextly's cache handlers in
17
+ * `next.config.ts`, as this dev app ships:
18
+ * https://nextlyhq.com/docs/guides/isr-caching#cache-components
19
+ */
20
+ import { cacheLife, cacheTag } from "next/cache";
21
+ import { getNextly, registerNextlyStart } from "nextly";
22
+ import { registerCachedRead, runRegisteredRead } from "nextly/runtime";
23
+
24
+ import config from "../../nextly.config";
25
+
26
+ export async function cachedRead(
27
+ key: string,
28
+ tags: string[],
29
+ revalidate: number | false
30
+ ): Promise<unknown> {
31
+ "use cache";
32
+ cacheTag(...tags);
33
+ // `revalidate: false` is tag-only, as everywhere in Nextly: only a write
34
+ // expires the entry, never time. Next.js's "max" profile revalidates monthly.
35
+ if (revalidate === false) {
36
+ cacheLife({ revalidate: Infinity, expire: Infinity });
37
+ } else {
38
+ // Expired at twice the revalidate, and never within 5 minutes: Next.js
39
+ // leaves an entry that expires sooner out of a static prerender.
40
+ cacheLife({ revalidate, expire: Math.max(revalidate * 2, 300) });
41
+ }
42
+ return runRegisteredRead(key);
43
+ }
44
+
45
+ registerCachedRead(cachedRead, process.env.__NEXT_CACHE_COMPONENTS);
46
+
47
+ // Tells Nextly how this app starts it, for a read that finds it not started,
48
+ // such as the root layout's `<SiteCode>` on `/_not-found` while `next build`
49
+ // prerenders, where nothing else starts it. Nothing starts here: a page that
50
+ // calls `getNextly` first is the start that counts. An app that passes
51
+ // `getNextly` more than its config passes the same options here.
52
+ registerNextlyStart(() => getNextly({ config }));
@@ -3,7 +3,7 @@
3
3
  * string (pages, settings, views, slots, field-type editors) so they load
4
4
  * without manual host imports. */
5
5
 
6
- import { registerComponents } from "@nextlyhq/admin";
6
+ import { registerComponentMap } from "@nextlyhq/admin";
7
7
  import * as _p0 from "{{pluginName}}/admin";
8
8
  import { registeredComponentPaths as listed } from "./plugin-admin-components.generated";
9
9
 
@@ -33,5 +33,5 @@ if (
33
33
  );
34
34
  }
35
35
 
36
- registerComponents(registered);
36
+ registerComponentMap(registered);
37
37
  export const registeredComponentPaths = listed;
@@ -1,24 +1,14 @@
1
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.
2
+ * Admin component registration. Importing this module registers the React
3
+ * components referenced by `contributes.admin` string paths so the host admin
4
+ * can resolve them. The host's generated component map imports it for you.
6
5
  */
7
- import {
8
- registerComponents,
9
- registerKnownPlugin,
10
- } from "@nextlyhq/plugin-sdk/admin";
6
+ import { registerComponents } from "@nextlyhq/plugin-sdk/admin";
11
7
 
12
8
  import { SettingsPage } from "./SettingsPage";
13
9
 
14
10
  const SETTINGS_PATH = "{{pluginName}}/admin#SettingsPage";
15
11
 
16
- // Eager: register on module load.
17
12
  registerComponents({ [SETTINGS_PATH]: SettingsPage });
18
13
 
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
14
  export { SettingsPage };
@@ -1,24 +0,0 @@
1
- import { connection } from "next/server";
2
- import { Suspense } from "react";
3
-
4
- export default function AdminLayout({
5
- children,
6
- }: {
7
- children: React.ReactNode;
8
- }) {
9
- return (
10
- <Suspense fallback={null}>
11
- <ForThisRequest>{children}</ForThisRequest>
12
- </Suspense>
13
- );
14
- }
15
-
16
- /**
17
- * The admin renders for each request, never from a cache another user's
18
- * request filled. Beneath the layout's Suspense boundary, so under Cache
19
- * Components the rest of the page still prerenders.
20
- */
21
- async function ForThisRequest({ children }: { children: React.ReactNode }) {
22
- await connection();
23
- return children;
24
- }