@mapled/cli 0.12.0 → 0.14.0
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 +2 -2
- package/dist/doctor.js +99 -6
- package/dist/live.d.ts +206 -40
- package/dist/live.js +1370 -314
- package/dist/scan.d.ts +8 -3
- package/dist/scan.js +68 -12
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -128,7 +128,7 @@ Scanned 2 HTML files — 2 pages, 11 bindings.
|
|
|
128
128
|
⚠ This path names nothing in the schema: home.tagline (index.html). A misspelled field empties its place on the page, a misspelled single leaves the page's own HTML.
|
|
129
129
|
```
|
|
130
130
|
|
|
131
|
-
For a Next.js site the scan also records its live updates: `capabilities.realtime` is `true`, with the route in `releaseRoute`, when `<MapledLive />` or `watchRelease` asks — at the default path or one written in the code — a route whose exported `GET` answers with `createReleaseHandler`, and `false` otherwise — `MAPLED.md` then tells the next agent whether open tabs follow Publish. Capabilities aren't part of the integration hash; `doctor`'s Site manifest line says when the pushed ones differ.
|
|
131
|
+
For a Next.js site the scan also records its live updates: `capabilities.realtime` is `true`, with the route in `releaseRoute`, when `<MapledLive />` or `watchRelease` asks — at the default path or one written in the code — a route whose exported `GET` answers with `createReleaseHandler`, or, on the Pages Router, an API route whose default export answers with `createPagesReleaseHandler` (with `<MapledLive />` from `@mapled/next/live/pages` in `pages/_app`), and `false` otherwise — `MAPLED.md` then tells the next agent whether open tabs follow Publish. Capabilities aren't part of the integration hash; `doctor`'s Site manifest line says when the pushed ones differ.
|
|
132
132
|
|
|
133
133
|
`--write` creates or updates `mapled/manifest.json`. Bindings the scan can't see (written by your AI agent, or by hand) are kept and listed; `--prune` drops them. Commit the file.
|
|
134
134
|
|
|
@@ -204,7 +204,7 @@ Mapled doctor — Dolphin Landing
|
|
|
204
204
|
1 warning.
|
|
205
205
|
```
|
|
206
206
|
|
|
207
|
-
The repository side: the link, the sign-in, the generated types, the schema pin, the site manifest (and whether it matches what was pushed), the integration hash — one line saying whether the pin and the manifest here describe the same integration Mapled holds, and if not, what moved (the schema, the bindings, or edits made in Mapled) and which command catches up — `MAPLED.md` (and whether it is what Mapled would render now), `MAPLED_KEY` and `MAPLED_WEBHOOK_SECRET` in your env files (names only — values are never read out), env files or Mapled secrets tracked by git, the installed `@mapled/next` and CLI versions, the revalidation and preview routes of a Next.js site, and its live updates — ○ «Not used» while no page mounts `<MapledLive />` or calls `watchRelease`, ✓ when the route they ask answers its `GET` with `createReleaseHandler` (`GET` is the handler, or always returns its call: every return is that call, the last statement is one, and nothing throws), ⚠ when it is missing (the tabs would poll a 404) with the file to add, when the route's `GET` isn't seen to answer with the handler (exported as `POST` they would get a 405; a `GET` that only mentions it, calls it and returns something else, or answers something else on another branch, answers without the release), or when no route can be checked against the endpoint — it is relative to the page, a full address (the tab asks it as written, and doctor can't tell it's the site's own), computed, or comes from spread props; a path from the site's root is checked against the route the App Router picks for it, dynamic and catch-all folders included, among route files with the site's page extensions (`pageExtensions` in next.config; `tsx`, `ts`, `jsx`, `js` by default) and under its `basePath`; ⚠ too when a page of either router, more specific than the route, takes that path first, when the dynamic route that answers sets `dynamicParams`, or when next.config may set `pageExtensions` or `
|
|
207
|
+
The repository side: the link, the sign-in, the generated types, the schema pin, the site manifest (and whether it matches what was pushed), the integration hash — one line saying whether the pin and the manifest here describe the same integration Mapled holds, and if not, what moved (the schema, the bindings, or edits made in Mapled) and which command catches up — `MAPLED.md` (and whether it is what Mapled would render now), `MAPLED_KEY` and `MAPLED_WEBHOOK_SECRET` in your env files (names only — values are never read out), env files or Mapled secrets tracked by git, the installed `@mapled/next` and CLI versions, the revalidation and preview routes of a Next.js site, and its live updates — ○ «Not used» while no page mounts `<MapledLive />` or calls `watchRelease`, ✓ when the route they ask answers its `GET` with `createReleaseHandler` (`GET` is the handler, or always returns its call: every return is that call, the last statement is one, and nothing throws), ⚠ when it is missing (the tabs would poll a 404) with the file to add, when the route's `GET` isn't seen to answer with the handler (exported as `POST` they would get a 405; a `GET` that only mentions it, calls it and returns something else, or answers something else on another branch, answers without the release), or when no route can be checked against the endpoint — it is relative to the page, a full address (the tab asks it as written, and doctor can't tell it's the site's own), computed, or comes from spread props; a path from the site's root is checked against the route the App Router picks for it, dynamic and catch-all folders included, among route files with the site's page extensions (`pageExtensions` in next.config; `tsx`, `ts`, `jsx`, `js` by default) and under its `basePath`; ⚠ too when a page of either router, more specific than the route, takes that path first, when the dynamic route that answers sets `dynamicParams`, when the route may set `dynamic` to anything but `"auto"` or `"force-dynamic"` (a copy Next builds without the tab's request), sets `"force-dynamic"` while `createReleaseHandler` there isn't seen to keep a `revalidate` above 0 (its default does; with `false` or `0` Next reads the release past the cache tag) or `fetchCache` to anything but `"auto"` (the release read past the cache tag, before the webhook refreshes the pages), when next.config sets `output: "export"` (no server runs the route), or when next.config may set `pageExtensions`, `basePath` or `output` where doctor can't read them — it reads them only as literals, once, in the object the file exports, and only while the file is declarative: type imports, declarations of data, one export of the config object itself, and functions only as Next's hooks (`webpack`, `headers` …) in it. A module the file loads (a plugin such as `withMDX`, a `path` helper, an env check), a config exported as a function, any other code, a spread or a getter leaves the config unread. With a `basePath`, a listener that gives no endpoint asks under it from `@mapled/next` 0.10.0 — doctor reads the version the site has installed, and it's a ⚠ when that is older or missing. A `middleware` file (`proxy` in Next 16) next to the folder Next reads is a ⚠ unless its matcher, read the way Next's build reads `export const config`, surely leaves the route's path alone: it has no matcher, doctor can't read it, a source may take the path, or next.config has locales (`i18n`). `src/app` or `src/pages` counts only when the root has no `app` or `pages`, as Next reads them. On the Pages Router the route is an API route — a file of the pages folder at `/api` or under it — whose default export is `createPagesReleaseHandler` or always returns its call, and the listener is `<MapledLive />` from `@mapled/next/live/pages` in `pages/_app`: a listener without it gets the API route to add (`pages/api/mapled/release.ts` by default; an endpoint outside `/api` no API route can answer). ⚠ there too when the API route may run on the edge runtime — `runtime` or `config.runtime` other than `"nodejs"`, or written where doctor can't read it (Next reads both only as literals) — where the handler has no Node request and response; when a listener the App Router may run asks it — a file of `app/`, or on a site with both folders one either router may render: the API route answers Mapled's live release, which moves before the webhook refreshes what App Router pages read; and when `<MapledLive />` from `@mapled/next/live/pages` sits in the App Router's folder, where it can't mount. doctor reads the files, not how Next serves them at run time: rewrites and what a middleware does on the path aren't checked, and neither is whether a page renders the component that listens; `revalidate` on the route is no ⚠ — the handler's read names its own. The Mapled side, from the same status the AI agent's `check_integration` tool reads: whether the site has read content with the delivery key, the publish webhook and its last delivery, the preview route on the deployed site, the bindings health.
|
|
208
208
|
|
|
209
209
|
A site rendered in the visitor's browser — a React single-page app (Vite and the like), plain HTML — is checked for what it has: the package line is `@mapled/react`, Environment wants `VITE_MAPLED_KEY` and no webhook secret, and the routes and the webhook read ○ «Not needed» — such a site shows a publish on its next load and trades Mapled's preview link on the page itself; Live updates reads ○ as well: `live` on `MapledProvider` or `data-mapled-live` asks Mapled directly, with no route of the site's. Plain HTML installs nothing: the line is `@mapled/vanilla`, read from the script tag in the pages (or from a copy of the file next to them) — ⚠ when the address has no version or an older one — and Generated types and Environment read ○: the fields are named in attributes, the delivery key sits in the page. doctor takes the framework from `mapled.json`, or works it out from `package.json` (no `package.json` and an `index.html` is plain HTML). Whatever the framework, a server key or a signing secret under a public prefix (`VITE_…`, `NEXT_PUBLIC_…`) is a ✗: it would ship to every visitor.
|
|
210
210
|
|
package/dist/doctor.js
CHANGED
|
@@ -3,7 +3,7 @@ import { access, readdir, readFile } from "node:fs/promises";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { promisify } from "node:util";
|
|
5
5
|
import { integrationHash } from "./integrationHash.js";
|
|
6
|
-
import { fullAddress, liveState, RELEASE_ROUTE, routeFileFor } from "./live.js";
|
|
6
|
+
import { BASE_PATH_SINCE, fullAddress, liveState, pagesRouteFileFor, RELEASE_ROUTE, routeFileFor, runsIn } from "./live.js";
|
|
7
7
|
import { compareManifests, manifestSummary, MANIFEST_FILE } from "./manifest.js";
|
|
8
8
|
import { SCHEMA_FILE } from "./pin.js";
|
|
9
9
|
import { splitGenerated } from "./types.js";
|
|
@@ -467,6 +467,10 @@ export function checkLive(facts, framework) {
|
|
|
467
467
|
const state = liveState(facts);
|
|
468
468
|
const who = (l) => `${l.via} in ${l.file}`;
|
|
469
469
|
const handler = 'createReleaseHandler from "@mapled/next/server"';
|
|
470
|
+
// the Pages Router's: the default export of an API route
|
|
471
|
+
const pagesHandler = 'createPagesReleaseHandler from "@mapled/next/server"';
|
|
472
|
+
const handlerOf = (route) => (route.router === "pages" ? pagesHandler : handler);
|
|
473
|
+
const app = `${facts.pagesDir ?? "pages"}/_app`;
|
|
470
474
|
switch (state.kind) {
|
|
471
475
|
case "unused":
|
|
472
476
|
return {
|
|
@@ -474,19 +478,46 @@ export function checkLive(facts, framework) {
|
|
|
474
478
|
label,
|
|
475
479
|
status: "skipped",
|
|
476
480
|
detail: state.handlerRoute
|
|
477
|
-
?
|
|
481
|
+
? state.handlerRoute.router === "pages"
|
|
482
|
+
? `Not used: ${state.handlerRoute.file} is mounted, but no page listens — add <MapledLive /> from "@mapled/next/live/pages" to ${app}.`
|
|
483
|
+
: `Not used: ${state.handlerRoute.file} is mounted, but no page listens — add <MapledLive /> to the root layout.`
|
|
478
484
|
: "Not used: an open tab shows a publish once it is reloaded.",
|
|
479
485
|
};
|
|
480
486
|
case "served":
|
|
481
487
|
return { key, label, status: "passed", detail: `${state.route.file} — open tabs follow Publish (${who(state.listener)})` };
|
|
482
488
|
case "no_handler":
|
|
483
|
-
return { key, label, status: "warning", detail: `${state.route.file} exists but doesn't use ${
|
|
489
|
+
return { key, label, status: "warning", detail: `${state.route.file} exists but doesn't use ${handlerOf(state.route)} — ${who(state.listener)} can't read the release there.` };
|
|
484
490
|
case "not_get":
|
|
485
491
|
return {
|
|
486
492
|
key,
|
|
487
493
|
label,
|
|
488
494
|
status: "warning",
|
|
489
|
-
detail:
|
|
495
|
+
detail: state.route.router === "pages"
|
|
496
|
+
? `${state.route.file} calls ${pagesHandler}, but doctor can't see its default export always answer with the handler, and the default export is what answers ${who(state.listener)}. Export the handler as the default, or end the default function by returning its call, with no other return or throw.`
|
|
497
|
+
: `${state.route.file} calls ${handler}, but doctor can't see its GET always answer with the handler's response — ${who(state.listener)} asks with GET. Export the handler as GET, or end GET by returning its call, with no other return or throw.`,
|
|
498
|
+
};
|
|
499
|
+
case "router":
|
|
500
|
+
return {
|
|
501
|
+
key,
|
|
502
|
+
label,
|
|
503
|
+
status: "warning",
|
|
504
|
+
detail: `${who(state.listener)} comes from "@mapled/next/live/pages", the Pages Router's component, and ${state.dir}/ is the App Router's folder: it can't mount there (it needs next/router), so open tabs don't follow Publish. Import MapledLive from "@mapled/next/live" in the App Router.`,
|
|
505
|
+
};
|
|
506
|
+
case "pointer":
|
|
507
|
+
return {
|
|
508
|
+
key,
|
|
509
|
+
label,
|
|
510
|
+
status: "warning",
|
|
511
|
+
detail: `${state.route.file} ${state.route.handler === "GET" ? "answers with" : "calls"} ${pagesHandler}, which follows Mapled's live release: it moves the moment someone publishes, before the webhook refreshes what the App Router's pages read. ${who(state.listener)} ${runsIn(state.listener, facts) === "app" ? "runs" : "may run"} in the App Router and would refresh into a release those pages don't show yet. Mount ${handler} at ${routeFileFor(state.path, facts)} in its place — or, if only the Pages Router renders the listener, use <MapledLive /> from "@mapled/next/live/pages" in ${app}.`,
|
|
512
|
+
};
|
|
513
|
+
case "runtime":
|
|
514
|
+
return {
|
|
515
|
+
key,
|
|
516
|
+
label,
|
|
517
|
+
status: "warning",
|
|
518
|
+
detail: state.runtime === null
|
|
519
|
+
? `${state.route.file} may set runtime where doctor can't read it — Next reads it from export const runtime or export const config = { runtime }, written with literals. On the edge runtime ${pagesHandler} has no Node request and response to answer ${who(state.listener)} through: write runtime as "nodejs", or leave it out.`
|
|
520
|
+
: `${state.route.file} sets runtime to ${JSON.stringify(state.runtime)} — ${pagesHandler} answers through Node's request and response, which an API route has only on the Node runtime, so ${who(state.listener)} gets an error there. Leave runtime out of the route, or set it to "nodejs".`,
|
|
490
521
|
};
|
|
491
522
|
case "unresolved":
|
|
492
523
|
return {
|
|
@@ -504,7 +535,16 @@ export function checkLive(facts, framework) {
|
|
|
504
535
|
key,
|
|
505
536
|
label,
|
|
506
537
|
status: "warning",
|
|
507
|
-
detail: `${state.file} may set pageExtensions or
|
|
538
|
+
detail: `${state.file} may set pageExtensions, basePath or output where doctor can't read them — code that runs as Next loads it, a plugin's or a function's result, a value or name the code builds, an object from another file or spread in — so no route can be checked for ${who(state.listener)}. Write them as literals in the object it exports, or leave them out.`,
|
|
539
|
+
};
|
|
540
|
+
case "export":
|
|
541
|
+
return {
|
|
542
|
+
key,
|
|
543
|
+
label,
|
|
544
|
+
status: "warning",
|
|
545
|
+
detail: state.output === "export"
|
|
546
|
+
? `${state.file} sets output: "export" — a static export has no server, so no route answers ${who(state.listener)}, and open tabs can't follow Publish. Live updates need the site served by Next: next start, or a host that runs it.`
|
|
547
|
+
: `${state.file} may set output where doctor can't read it — with output: "export" the site has no server, and no route answers ${who(state.listener)}. Write output as a literal in the object it exports, or leave it out.`,
|
|
508
548
|
};
|
|
509
549
|
case "outside":
|
|
510
550
|
return {
|
|
@@ -513,6 +553,15 @@ export function checkLive(facts, framework) {
|
|
|
513
553
|
status: "warning",
|
|
514
554
|
detail: `${who(state.listener)} asks ${state.endpoint}, outside the site's basePath ${state.basePath} — Next answers nothing there. Give it the path under the basePath: ${state.basePath}${state.endpoint === "/" ? "" : state.endpoint}.`,
|
|
515
555
|
};
|
|
556
|
+
case "default_base":
|
|
557
|
+
return {
|
|
558
|
+
key,
|
|
559
|
+
label,
|
|
560
|
+
status: "warning",
|
|
561
|
+
detail: state.sdk === null
|
|
562
|
+
? `${who(state.listener)} gives no endpoint, and doctor finds no @mapled/next in node_modules to tell where it asks: from ${BASE_PATH_SINCE} under the site's basePath (${state.basePath}${RELEASE_ROUTE}), before that at ${RELEASE_ROUTE}, where Next answers nothing. Run npm install, or give it the path: ${state.basePath}${RELEASE_ROUTE}.`
|
|
563
|
+
: `${who(state.listener)} gives no endpoint, so @mapled/next ${state.sdk} asks ${RELEASE_ROUTE}, outside the site's basePath ${state.basePath} — Next answers nothing there. Update to @mapled/next ${BASE_PATH_SINCE} or later, which asks under the basePath, or give it the path: ${state.basePath}${RELEASE_ROUTE}.`,
|
|
564
|
+
};
|
|
516
565
|
case "taken":
|
|
517
566
|
return {
|
|
518
567
|
key,
|
|
@@ -527,7 +576,50 @@ export function checkLive(facts, framework) {
|
|
|
527
576
|
status: "warning",
|
|
528
577
|
detail: `${state.route.file} sets dynamicParams, so it answers only the params its code lists — doctor can't tell ${state.endpoint} is one, and ${who(state.listener)} may get a 404 there. Mount ${handler} at ${routeFileFor(state.endpoint, facts)}.`,
|
|
529
578
|
};
|
|
530
|
-
case "
|
|
579
|
+
case "segment":
|
|
580
|
+
return {
|
|
581
|
+
key,
|
|
582
|
+
label,
|
|
583
|
+
status: "warning",
|
|
584
|
+
detail: state.setting === "dynamic"
|
|
585
|
+
? `${state.route.file} may set dynamic to something other than "auto" or "force-dynamic" — with "force-static" Next serves a copy of the route built without the tab's request, which moves only when Next rebuilds it and answers every check in full, so doctor can't tell ${who(state.listener)} follows Publish. Leave dynamic out of the route.`
|
|
586
|
+
: state.setting === "force-dynamic"
|
|
587
|
+
? `${state.route.file} sets dynamic to "force-dynamic", and doctor can't see createReleaseHandler there keep a revalidate above 0 (false, 0, or options it can't read) — Next then reads the live release past the cache tag, so the route moves before the webhook refreshes the pages, and ${who(state.listener)} refreshes into a release they don't show yet. Leave dynamic out of the route, or revalidate at its default.`
|
|
588
|
+
: `${state.route.file} may set fetchCache to something other than "auto" — with "force-no-store" the handler reads the live release past the cache tag, so the route moves before the webhook refreshes the pages, and ${who(state.listener)} refreshes into a release they don't show yet. Leave fetchCache out of the route.`,
|
|
589
|
+
};
|
|
590
|
+
case "middleware": {
|
|
591
|
+
const base = facts.basePath ?? "";
|
|
592
|
+
const local = base !== "" && state.path.startsWith(base) ? state.path.slice(base.length) || "/" : state.path;
|
|
593
|
+
const why = state.reason === "none"
|
|
594
|
+
? `${state.file} has no matcher, so it runs on every path, ${state.path} included`
|
|
595
|
+
: state.reason === "unread"
|
|
596
|
+
? `doctor can't read the matcher of ${state.file} — Next reads it only from export const config = { matcher: … } written with literals — so it may run on ${state.path}`
|
|
597
|
+
: state.reason === "i18n"
|
|
598
|
+
? `${state.file} may run on ${state.path}: with i18n in next.config Next puts a locale in front of its matcher, and doctor can't tell which paths it takes`
|
|
599
|
+
: `${state.file} may run on ${state.path}: its matcher ${JSON.stringify(state.source)} can take it`;
|
|
600
|
+
return {
|
|
601
|
+
key,
|
|
602
|
+
label,
|
|
603
|
+
status: "warning",
|
|
604
|
+
detail: `${why}. doctor can't see what it does there — a redirect, a rewrite or a sign-in wall keeps ${who(state.listener)} from the release. Leave the path out of its matcher, like "/((?!${local.slice(1)}).*)".`,
|
|
605
|
+
};
|
|
606
|
+
}
|
|
607
|
+
case "missing": {
|
|
608
|
+
if (state.pages) {
|
|
609
|
+
// the Pages Router: an API route, whose default export is the handler
|
|
610
|
+
const base = facts.basePath ?? "";
|
|
611
|
+
const file = state.endpoint === null ? null : pagesRouteFileFor(state.endpoint, facts);
|
|
612
|
+
return {
|
|
613
|
+
key,
|
|
614
|
+
label,
|
|
615
|
+
status: "warning",
|
|
616
|
+
detail: state.endpoint === null
|
|
617
|
+
? `${who(state.listener)} asks a release route the code doesn't have — export ${pagesHandler} as the default of an API route (${pagesRouteFileFor(RELEASE_ROUTE, facts)} by default).`
|
|
618
|
+
: file !== null
|
|
619
|
+
? `${who(state.listener)} asks ${state.endpoint}, which has no route — export ${pagesHandler} as the default of ${file}.`
|
|
620
|
+
: `${who(state.listener)} asks ${state.endpoint}, which has no route — and in the Pages Router only an API route answers it, at ${base}/api or under it. Point it at ${base}${RELEASE_ROUTE}, and export ${pagesHandler} as the default of ${pagesRouteFileFor(`${base}${RELEASE_ROUTE}`, facts)}.`,
|
|
621
|
+
};
|
|
622
|
+
}
|
|
531
623
|
return state.endpoint === null
|
|
532
624
|
? {
|
|
533
625
|
key,
|
|
@@ -541,6 +633,7 @@ export function checkLive(facts, framework) {
|
|
|
541
633
|
status: "warning",
|
|
542
634
|
detail: `${who(state.listener)} asks ${state.endpoint}, which has no route — mount ${handler} at ${routeFileFor(state.endpoint, facts)}.`,
|
|
543
635
|
};
|
|
636
|
+
}
|
|
544
637
|
}
|
|
545
638
|
}
|
|
546
639
|
export function checkReads(lastReadAt) {
|
package/dist/live.d.ts
CHANGED
|
@@ -2,58 +2,110 @@
|
|
|
2
2
|
follows Publish in open tabs with two pieces: `<MapledLive />` from
|
|
3
3
|
"@mapled/next/live" (or `watchRelease` from "@mapled/next") asks a
|
|
4
4
|
route of the site every few seconds, and that route answers with
|
|
5
|
-
`createReleaseHandler` from "@mapled/next/server".
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
5
|
+
`createReleaseHandler` from "@mapled/next/server". The Pages Router
|
|
6
|
+
has its own two: `<MapledLive />` from "@mapled/next/live/pages", and
|
|
7
|
+
an API route whose default export is `createPagesReleaseHandler`. A
|
|
8
|
+
listener without the route polls a 404 forever and nothing says so —
|
|
9
|
+
`mapled doctor` reads both and says which is missing, and `mapled
|
|
10
|
+
scan` records the result in the manifest (`capabilities.realtime`,
|
|
11
|
+
`releaseRoute`) so MAPLED.md can tell the next agent. The code is read
|
|
12
|
+
with the scanner's tokenizer: comments and strings never count as a
|
|
13
|
+
use, and neither does an imported name the file binds again. */
|
|
12
14
|
/** Where `<MapledLive />` and `watchRelease` ask when no endpoint is given. */
|
|
13
15
|
export declare const RELEASE_ROUTE = "/api/mapled/release";
|
|
16
|
+
/** The `@mapled/next` from which that default follows the site's basePath —
|
|
17
|
+
before it, a listener with no endpoint asks RELEASE_ROUTE as written. */
|
|
18
|
+
export declare const BASE_PATH_SINCE = "0.10.0";
|
|
14
19
|
export type LiveListener = {
|
|
15
20
|
file: string;
|
|
16
21
|
via: "<MapledLive />" | "watchRelease";
|
|
17
22
|
/** the endpoint as the code writes it; null when the code decides it —
|
|
18
23
|
a computed value, or spread props or options that may carry one */
|
|
19
24
|
endpoint: string | null;
|
|
25
|
+
/** the code gives no endpoint: the SDK asks its default, `endpoint` —
|
|
26
|
+
under the site's basePath from `@mapled/next` 0.10.0 */
|
|
27
|
+
byDefault?: true;
|
|
28
|
+
/** `<MapledLive />` from "@mapled/next/live/pages": the Pages Router's
|
|
29
|
+
component, which only the Pages Router can mount (`next/router`) */
|
|
30
|
+
router?: "pages";
|
|
20
31
|
};
|
|
21
|
-
/**
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
32
|
+
/** A route file and the path it answers: an App Router route handler
|
|
33
|
+
file, or a Pages Router API route (`router: "pages"`) — any file of the
|
|
34
|
+
pages folder whose path is /api or under it. `handler`: "GET" when the
|
|
35
|
+
tab's GET is seen to reach the release — the App Router route's
|
|
36
|
+
exported GET answers with `createReleaseHandler`, the API route's
|
|
37
|
+
default export with `createPagesReleaseHandler` — "other" when the file
|
|
38
|
+
calls that factory but its answer isn't seen to be the handler's (the
|
|
39
|
+
App Router handler exported as POST or not at all — the tab's GET gets
|
|
40
|
+
a 405 — or a GET or a default export that answers something else),
|
|
41
|
+
null when it doesn't call it. `params`: the file sets `dynamicParams`
|
|
42
|
+
— a dynamic route then answers only the params its code lists.
|
|
43
|
+
`segment`: the first route segment setting the file may export as a
|
|
44
|
+
value that changes how Next serves or caches the route
|
|
45
|
+
(SEGMENT_SETTINGS). Both are the App Router's; an API route has
|
|
46
|
+
`runtime` instead: present when the file may run it on another runtime
|
|
47
|
+
than Node's — the runtime it writes out, or null where the reading
|
|
48
|
+
can't tell (pagesRuntime). */
|
|
28
49
|
export type RouteFile = {
|
|
29
50
|
file: string;
|
|
30
51
|
path: string;
|
|
31
52
|
handler: RouteHandler;
|
|
32
53
|
params?: true;
|
|
54
|
+
segment?: SegmentSetting;
|
|
55
|
+
router?: "pages";
|
|
56
|
+
runtime?: string | null;
|
|
33
57
|
};
|
|
34
58
|
export type RouteHandler = "GET" | "other" | null;
|
|
35
|
-
/** A page file and the path it answers: an App Router `page.<ext>`, or
|
|
36
|
-
Pages Router
|
|
37
|
-
as Next makes it — a page more specific than a route takes
|
|
59
|
+
/** A page file and the path it answers: an App Router `page.<ext>`, or a
|
|
60
|
+
Pages Router page (its API routes are route files). It takes part in
|
|
61
|
+
the match as Next makes it — a page more specific than a route takes
|
|
62
|
+
the path. */
|
|
38
63
|
export type PageFile = {
|
|
39
64
|
file: string;
|
|
40
65
|
path: string;
|
|
41
66
|
};
|
|
42
67
|
/** `routeExtension`: what a route file the site lacks would end in — the
|
|
43
|
-
site's page extension, `ts` by default. `appDir`: the App
|
|
44
|
-
Next reads, when the site has
|
|
45
|
-
site starts with (next.config).
|
|
46
|
-
may set `pageExtensions` or
|
|
47
|
-
then no route can be
|
|
68
|
+
site's page extension, `ts` by default. `appDir`, `pagesDir`: the App
|
|
69
|
+
Router and Pages Router folders Next reads, when the site has them.
|
|
70
|
+
`basePath`: what every path of the site starts with (next.config).
|
|
71
|
+
`config`: the next.config file, when it may set `pageExtensions` or
|
|
72
|
+
`basePath` where the reading can't follow — then no route can be
|
|
73
|
+
checked. `sdk`: the `@mapled/next` version the site resolves, as Node
|
|
74
|
+
finds the package from the site's folder up — absent when none is
|
|
75
|
+
installed. `output`: next.config sets `output: "export"` (value
|
|
76
|
+
"export") or may set it where the reading can't tell (null) — then
|
|
77
|
+
there may be no server to run any route. */
|
|
48
78
|
export type LiveFacts = {
|
|
49
79
|
listeners: LiveListener[];
|
|
50
80
|
routes: RouteFile[];
|
|
51
81
|
pages?: PageFile[];
|
|
52
82
|
routeExtension?: string;
|
|
53
83
|
appDir?: string;
|
|
84
|
+
pagesDir?: string;
|
|
54
85
|
basePath?: string;
|
|
55
86
|
config?: string;
|
|
87
|
+
sdk?: string;
|
|
88
|
+
output?: {
|
|
89
|
+
file: string;
|
|
90
|
+
value: "export" | null;
|
|
91
|
+
};
|
|
92
|
+
middleware?: Middleware[];
|
|
93
|
+
i18n?: true;
|
|
56
94
|
};
|
|
95
|
+
/** A middleware file of the site (`proxy` from Next 16) and the paths it
|
|
96
|
+
runs on as Next reads its `config`: the matcher's sources, "none" when
|
|
97
|
+
it has no matcher — every path — or "unread" when Next may not read the
|
|
98
|
+
config doctor sees, or doctor can't read it. */
|
|
99
|
+
export type Middleware = {
|
|
100
|
+
file: string;
|
|
101
|
+
matcher: string[] | "none" | "unread";
|
|
102
|
+
};
|
|
103
|
+
/** `missing`: `pages` when the listener runs in the Pages Router — the
|
|
104
|
+
route it lacks is an API route there. `router`: the Pages Router's
|
|
105
|
+
component in a file of the App Router folder `dir`, where it can't
|
|
106
|
+
mount. `pointer`: an API route answers with `createPagesReleaseHandler`
|
|
107
|
+
a listener the App Router may run (runsIn). `runtime`: the API route
|
|
108
|
+
that answers may run on another runtime than Node's. */
|
|
57
109
|
export type LiveState = {
|
|
58
110
|
kind: "unused";
|
|
59
111
|
handlerRoute: RouteFile | null;
|
|
@@ -66,6 +118,21 @@ export type LiveState = {
|
|
|
66
118
|
kind: "missing";
|
|
67
119
|
listener: LiveListener;
|
|
68
120
|
endpoint: string | null;
|
|
121
|
+
pages?: true;
|
|
122
|
+
} | {
|
|
123
|
+
kind: "router";
|
|
124
|
+
listener: LiveListener;
|
|
125
|
+
dir: string;
|
|
126
|
+
} | {
|
|
127
|
+
kind: "pointer";
|
|
128
|
+
listener: LiveListener;
|
|
129
|
+
route: RouteFile;
|
|
130
|
+
path: string;
|
|
131
|
+
} | {
|
|
132
|
+
kind: "runtime";
|
|
133
|
+
listener: LiveListener;
|
|
134
|
+
route: RouteFile;
|
|
135
|
+
runtime: string | null;
|
|
69
136
|
} | {
|
|
70
137
|
kind: "unresolved";
|
|
71
138
|
listener: LiveListener;
|
|
@@ -86,6 +153,24 @@ export type LiveState = {
|
|
|
86
153
|
listener: LiveListener;
|
|
87
154
|
endpoint: string;
|
|
88
155
|
basePath: string;
|
|
156
|
+
} | {
|
|
157
|
+
kind: "default_base";
|
|
158
|
+
listener: LiveListener;
|
|
159
|
+
basePath: string;
|
|
160
|
+
sdk: string | null;
|
|
161
|
+
} | {
|
|
162
|
+
kind: "export";
|
|
163
|
+
listener: LiveListener;
|
|
164
|
+
file: string;
|
|
165
|
+
output: "export" | null;
|
|
166
|
+
} | {
|
|
167
|
+
kind: "middleware";
|
|
168
|
+
listener: LiveListener;
|
|
169
|
+
route: RouteFile;
|
|
170
|
+
path: string;
|
|
171
|
+
file: string;
|
|
172
|
+
reason: "none" | "unread" | "i18n" | "matcher";
|
|
173
|
+
source?: string;
|
|
89
174
|
} | {
|
|
90
175
|
kind: "taken";
|
|
91
176
|
listener: LiveListener;
|
|
@@ -96,6 +181,11 @@ export type LiveState = {
|
|
|
96
181
|
listener: LiveListener;
|
|
97
182
|
route: RouteFile;
|
|
98
183
|
endpoint: string;
|
|
184
|
+
} | {
|
|
185
|
+
kind: "segment";
|
|
186
|
+
listener: LiveListener;
|
|
187
|
+
route: RouteFile;
|
|
188
|
+
setting: SegmentSetting;
|
|
99
189
|
};
|
|
100
190
|
/** What the manifest records (§21.2). */
|
|
101
191
|
export type Capabilities = {
|
|
@@ -104,17 +194,32 @@ export type Capabilities = {
|
|
|
104
194
|
};
|
|
105
195
|
/** The extensions a route file may have when next.config doesn't set `pageExtensions`. */
|
|
106
196
|
export declare const PAGE_EXTENSIONS: string[];
|
|
107
|
-
/** The listeners one file mounts and, for a route file
|
|
108
|
-
serves the release
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
197
|
+
/** The listeners one file mounts and, for a route file of `router`,
|
|
198
|
+
whether it serves the release: an App Router route through its GET, a
|
|
199
|
+
Pages API route through its default export. An imported name the file
|
|
200
|
+
binds again — a parameter, a declaration, destructuring — may not be
|
|
201
|
+
the import where it's used, and the reading can't tell where: none of
|
|
202
|
+
its uses count. A namespace's name the same: its members count only
|
|
203
|
+
where `ns` itself is the import. */
|
|
204
|
+
export declare function liveInFile(file: string, text: string, router?: "app" | "pages"): {
|
|
114
205
|
listeners: LiveListener[];
|
|
115
206
|
handler: RouteHandler;
|
|
116
207
|
params: boolean;
|
|
208
|
+
segment?: SegmentSetting;
|
|
209
|
+
runtime?: string | null;
|
|
117
210
|
};
|
|
211
|
+
/** The route segment settings that change how Next serves the release
|
|
212
|
+
route or reads what the handler reads, in the order doctor names them:
|
|
213
|
+
`dynamic` can make it a static copy built without the tab's request,
|
|
214
|
+
and `fetchCache` can make the handler's read skip the `mapled` cache
|
|
215
|
+
tag, so the release moves before the webhook refreshes the pages.
|
|
216
|
+
`revalidate` isn't one: it only fills in fetches that don't name their
|
|
217
|
+
own window, and the handler's names one (Next 14's fetch patch; the
|
|
218
|
+
live smoke with `revalidate = 1` on Next 15). "force-dynamic" is the
|
|
219
|
+
third: harmless while the handler keeps its read's window, and past
|
|
220
|
+
the tag when it doesn't (keepsWindow). */
|
|
221
|
+
export declare const SEGMENT_SETTINGS: readonly ["dynamic", "fetchCache"];
|
|
222
|
+
export type SegmentSetting = (typeof SEGMENT_SETTINGS)[number] | "force-dynamic";
|
|
118
223
|
/** The route files of the App Router with the path each answers. */
|
|
119
224
|
export declare function routeFileOf(file: string, extensions?: string[]): string | null;
|
|
120
225
|
/** The page files of both routers with the path each answers: an App
|
|
@@ -124,28 +229,83 @@ export declare function routeFileOf(file: string, extensions?: string[]): string
|
|
|
124
229
|
export declare function pageFileOf(file: string, extensions?: string[]): string | null;
|
|
125
230
|
/** What next.config decides about where a file answers: the page
|
|
126
231
|
extensions of route and page files, and the basePath every path of the
|
|
127
|
-
site starts with.
|
|
232
|
+
site starts with. `output`, when the file may set it: the literal, or
|
|
233
|
+
null where the reading can't tell — with "export" there is no server.
|
|
234
|
+
`i18n`: the file mentions it, so locales may be configured. */
|
|
128
235
|
export type NextConfig = {
|
|
129
236
|
pageExtensions: string[];
|
|
130
237
|
basePath: string;
|
|
238
|
+
output?: string | null;
|
|
239
|
+
i18n?: true;
|
|
131
240
|
};
|
|
132
|
-
/** next.config's `pageExtensions` and `
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
241
|
+
/** next.config's `pageExtensions`, `basePath` and `output`, read only when
|
|
242
|
+
the file is written in a declarative subset of JavaScript, where no code
|
|
243
|
+
runs while Next loads it — neither the file's nor a module's:
|
|
244
|
+
|
|
245
|
+
- statements: type imports (`import type { NextConfig } from "next"`),
|
|
246
|
+
declarations, and one export of the config object itself —
|
|
247
|
+
`module.exports = {…}`, `export default nextConfig` or `export {
|
|
248
|
+
config as default }`;
|
|
249
|
+
- values: literals, and arrays and objects of them, `process.env.NAME`,
|
|
250
|
+
`process.cwd()`, `__dirname`, `import.meta.dirname`, `new URL(…)`,
|
|
251
|
+
`require.resolve("…")`, names declared so; `!`, `+`, comparisons,
|
|
252
|
+
`&&`, `||`, `??` and `? :` over them; a type after `as` or
|
|
253
|
+
`satisfies`;
|
|
254
|
+
- functions only as Next's hooks (`webpack`, `headers`, `redirects`,
|
|
255
|
+
`rewrites`, `generateBuildId`, `exportPathMap`) at the top of the
|
|
256
|
+
exported object: Next calls them once it has loaded the config.
|
|
257
|
+
|
|
258
|
+
Anything else makes it null: a module loaded in any way (`import "…"`,
|
|
259
|
+
`import x from "…"`, `require(…)`) — its code runs, and which module a
|
|
260
|
+
name loads its spelling doesn't show (Node's self-reference, a
|
|
261
|
+
next.config.ts's tsconfig `paths`); so a plugin's call around the
|
|
262
|
+
config (`withMDX(nextConfig)`) and a `path` helper too (block 348). And
|
|
263
|
+
any other statement, a function or class of the file, a getter, a
|
|
264
|
+
spread, a member of a value, a call, a name bound twice or over one the
|
|
265
|
+
subset reads (`require`, `process`), the config's name used twice. Each
|
|
266
|
+
setting: a literal, once, in that object — `pageExtensions` or
|
|
267
|
+
`basePath` named anywhere else in the file makes it null, and an
|
|
268
|
+
`output` the reading can't take (an expression, a second one, one a
|
|
269
|
+
hook's code writes) makes only `output` null. `live.test.ts` holds the
|
|
270
|
+
reading to Next's own `loadConfig` over generated configs.
|
|
271
|
+
|
|
272
|
+
What the reading trusts: Next calls the hooks only after loading, and a
|
|
273
|
+
hook leaves the settings it loaded alone. */
|
|
141
274
|
export declare function nextConfigOf(config: string | null): NextConfig | null;
|
|
275
|
+
/** The matcher of a middleware file, read as Next's build reads it: only
|
|
276
|
+
`export const config = { … }` (a type or `satisfies` allowed), every
|
|
277
|
+
value in it a literal (extractable), `matcher` a string or an array of
|
|
278
|
+
strings and `{ source }` objects. Anything else Next can't read either
|
|
279
|
+
and runs the middleware on every path — or reads what doctor can't. */
|
|
280
|
+
export declare function middlewareMatcher(text: string): string[] | "none" | "unread";
|
|
281
|
+
/** Whether a matcher source may take the path `at` (without the basePath,
|
|
282
|
+
which Next puts in front of both). False only when it surely can't: the
|
|
283
|
+
literal the source starts with isn't how the path starts, or right
|
|
284
|
+
after it comes a group — path-to-regexp's `(…)`, kept verbatim in the
|
|
285
|
+
regex Next builds — that matches no start of the rest, in either case.
|
|
286
|
+
Everything else may. */
|
|
287
|
+
export declare function sourceMayTake(source: string, at: string): boolean;
|
|
288
|
+
/** Whether a version is at least `min`, by its major, minor and patch —
|
|
289
|
+
false for one that doesn't start with three numbers. */
|
|
290
|
+
export declare function atLeast(version: string, min: string): boolean;
|
|
142
291
|
export declare function findLive(dir: string, maxFiles?: number): Promise<LiveFacts>;
|
|
143
292
|
/** Whether a literal endpoint names its own origin — `https://…`, `//host/…`
|
|
144
293
|
— which the code doesn't show to be the site's: `watchRelease` fetches it
|
|
145
294
|
as written, and no route file of this site is known to answer it. */
|
|
146
295
|
export declare const fullAddress: (endpoint: string) => boolean;
|
|
296
|
+
/** Which router runs a listener's code, as far as its file tells: a file
|
|
297
|
+
of the App Router folder is the App Router's; the Pages Router's
|
|
298
|
+
`<MapledLive />`, a file of the pages folder, or any file of a site
|
|
299
|
+
whose only router folder is the pages one — the Pages Router's. Null
|
|
300
|
+
for anything else: a file either router may render. */
|
|
301
|
+
export declare function runsIn(listener: LiveListener, facts: LiveFacts): "app" | "pages" | null;
|
|
147
302
|
/** Whether every listener has a route that answers it. The first one
|
|
148
|
-
that doesn't is the one to name.
|
|
303
|
+
that doesn't is the one to name. An API route answers with the live
|
|
304
|
+
release (`createPagesReleaseHandler`), which moves the moment someone
|
|
305
|
+
publishes: it serves only listeners the Pages Router runs, whose pages
|
|
306
|
+
read that same release in getServerSideProps (README). A listener the
|
|
307
|
+
App Router may run refreshes on it before the webhook refreshes what
|
|
308
|
+
the App Router's pages read, into a release they don't show yet. */
|
|
149
309
|
export declare function liveState(facts: LiveFacts): LiveState;
|
|
150
310
|
/** What `mapled scan` writes into the manifest: a listener nothing answers
|
|
151
311
|
isn't listening; the route is the path the tab asks, not a dynamic route's pattern. */
|
|
@@ -154,3 +314,9 @@ export declare function capabilitiesOf(state: LiveState): Capabilities;
|
|
|
154
314
|
reads — without one, under `src/app` when the site keeps its code there
|
|
155
315
|
— and without the basePath, which Next adds itself. */
|
|
156
316
|
export declare function routeFileFor(endpoint: string, facts: LiveFacts): string;
|
|
317
|
+
/** Where an API route answering `endpoint` goes, for a listener the Pages
|
|
318
|
+
Router runs: in the pages folder Next reads — without one, under
|
|
319
|
+
`src/pages` when the site keeps its code there — and without the
|
|
320
|
+
basePath. Null for a path no API route answers: Next serves them at
|
|
321
|
+
/api and under it only. */
|
|
322
|
+
export declare function pagesRouteFileFor(endpoint: string, facts: LiveFacts): string | null;
|