@mapled/cli 0.14.1 → 0.14.3

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 CHANGED
@@ -130,6 +130,8 @@ Scanned 2 HTML files — 2 pages, 11 bindings.
130
130
 
131
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
+ On a site with more than one language the scan records how the routes carry it: `locales` is `{ "routing": "prefix", "param": "locale" }` when the routes start with a segment named for the language (`/[locale]/blog/[slug]`) — Mapled then builds each language's address of a record from it (the editor's routes, Preview), a translated slug being the address in that language. Write `{ "routing": "prefix-except-default" }` by hand when the default language lives at the root and the others under `/ru/…`, `/de/…` (without `param` Mapled puts the language before the page); leave `locales` out when every language shares one address. It is part of the integration hash.
134
+
133
135
  `--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
136
 
135
137
  ## Validate and push the manifest
@@ -140,7 +142,7 @@ npx @mapled/cli bindings push
140
142
  npx @mapled/cli bindings pull
141
143
  ```
142
144
 
143
- `manifest validate` checks `mapled/manifest.json` the way Mapled will: the shape and limits, duplicate keys, and — against the schema pin or the live schema — collections and fields that don't exist and targets that don't fit the field type, plus files the repository doesn't have. Problems exit 1; warnings only tell you what Mapled would grade down.
145
+ `manifest validate` checks `mapled/manifest.json` the way Mapled will: the shape and limits, duplicate keys, `capabilities` and `locales` (`routing` and `param` only, as Mapled's schema takes them — and whether a page route has the `param` segment), and — against the schema pin or the live schema — collections and fields that don't exist and targets that don't fit the field type, plus files the repository doesn't have. Problems exit 1; warnings only tell you what Mapled would grade down.
144
146
 
145
147
  `bindings push` sends the manifest to Mapled (the same call your AI agent's `push_site_manifest` makes). Mapled grades every binding — healthy, type mismatch, outdated, missing on site — and the result shows up in **Structure → Bindings**. The push also records the integration hash: one marker over the schema and these bindings, what the site is synced with from now on. `--dry-run` validates and stops. Pushing bindings changes structure, so it needs the builder plan.
146
148
 
@@ -254,9 +256,10 @@ Still hardcoded
254
256
  ```bash
255
257
  npx @mapled/cli preview
256
258
  npx @mapled/cli preview articles hello-world
259
+ npx @mapled/cli preview articles hello-world --locale ru
257
260
  ```
258
261
 
259
- Prints a Preview link — the one the **Preview** button in Mapled opens: the site in draft mode, with every saved change that isn't published yet. Name a collection and a record's slug (or id) and the link lands on the record's page, the one the pushed site manifest says shows it; a collection alone opens the page that lists it, a single its page, nothing at all the home page. Mapled makes the link after checking that the site answers and mounts its preview route; it works once, for five minutes. `--open` opens it in your browser instead of printing it; `--json` prints the link with the page and when it expires.
262
+ Prints a Preview link — the one the **Preview** button in Mapled opens: the site in draft mode, with every saved change that isn't published yet. Name a collection and a record's slug (or id) and the link lands on the record's page, the one the pushed site manifest says shows it; a collection alone opens the page that lists it, a single its page, nothing at all the home page. Mapled makes the link after checking that the site answers and mounts its preview route; it works once, for five minutes. A plain HTML site whose pages are built with the content in them has no page for a record it wasn't built with: for such a record the command says so instead of printing a link — its page appears once the site is built with it. `--locale <code>` opens the page in that language — the address Mapled builds from the manifest's `locales` (a translated slug is the address in its language); a language the project doesn't have is refused. `--open` opens it in your browser instead of printing it; `--json` prints the link with the page, the language when one was asked, and when it expires.
260
263
 
261
264
  ## Options
262
265
 
package/dist/args.js CHANGED
@@ -2,7 +2,7 @@ import { CliError } from "./errors.js";
2
2
  /** Minimal argument parsing: the leading words name the command;
3
3
  `--flag`, `--flag=value` and `--flag value` (for the flags that take
4
4
  one) follow. Nothing here is user content beyond the command line. */
5
- const VALUE_FLAGS = new Set(["api", "project", "out", "key"]);
5
+ const VALUE_FLAGS = new Set(["api", "project", "out", "key", "locale"]);
6
6
  export function parseArgs(argv) {
7
7
  const words = [];
8
8
  const flags = {};
@@ -38,10 +38,12 @@ export declare function formsList(ctx: Ctx, flags: Flags): Promise<void>;
38
38
  applied setup run (S-42) and what the agent reported; `--verify` runs
39
39
  the server checks again first, as "Run checks again" does. */
40
40
  export declare function integrationReport(ctx: Ctx, flags: Flags): Promise<number>;
41
- /** `mapled preview [<collection> [<slug>]]` — a Preview link from Mapled's
42
- launcher, the one the Preview button opens: the token is Mapled's, the
43
- CLI only passes it on. `--open` hands it to the browser instead of
44
- printing it (the link works once). */
41
+ /** `mapled preview [<collection> [<slug>]] [--locale <code>]` — a Preview
42
+ link from Mapled's launcher, the one the Preview button opens: the token
43
+ is Mapled's, the CLI only passes it on. `--locale` opens the page in that
44
+ language (§28.4: Mapled builds the address from the manifest's `locales`;
45
+ a language the project doesn't have is refused by Mapled). `--open` hands
46
+ it to the browser instead of printing it (the link works once). */
45
47
  export declare function preview(ctx: Ctx, flags: Flags, words: string[]): Promise<void>;
46
48
  export declare function scan(ctx: Ctx, flags: Flags): Promise<void>;
47
49
  export declare function doctor(ctx: Ctx, flags: Flags): Promise<number>;
package/dist/commands.js CHANGED
@@ -360,7 +360,7 @@ export async function bindingsPull(ctx, flags) {
360
360
  if (local && !flags.force) {
361
361
  const diff = compareManifests(local.manifest, stored.manifest);
362
362
  // capabilities a scan just recorded are as much the repository's as its bindings
363
- if (!diff.same || diff.capabilitiesDiffer) {
363
+ if (!diff.same || diff.capabilitiesDiffer || diff.localesDiffer) {
364
364
  const parts = [];
365
365
  if (diff.onlyLocal.length > 0)
366
366
  parts.push(`${diff.onlyLocal.length} not pushed`);
@@ -370,6 +370,8 @@ export async function bindingsPull(ctx, flags) {
370
370
  parts.push(`${diff.onlyRemote.length} only in Mapled`);
371
371
  if (diff.capabilitiesDiffer)
372
372
  parts.push("capabilities changed");
373
+ if (diff.localesDiffer)
374
+ parts.push("routes by language changed");
373
375
  throw new CliError(`${MANIFEST_FILE} differs from the pushed manifest (v${stored.version}): ${parts.join(", ")}. Pass --force to overwrite it, or push yours with \`mapled bindings push\`.`);
374
376
  }
375
377
  }
@@ -478,21 +480,28 @@ export async function integrationReport(ctx, flags) {
478
480
  ctx.out(line);
479
481
  return report.checks.some((c) => c.status === "failed") ? 1 : 0;
480
482
  }
481
- /** `mapled preview [<collection> [<slug>]]` — a Preview link from Mapled's
482
- launcher, the one the Preview button opens: the token is Mapled's, the
483
- CLI only passes it on. `--open` hands it to the browser instead of
484
- printing it (the link works once). */
483
+ /** `mapled preview [<collection> [<slug>]] [--locale <code>]` — a Preview
484
+ link from Mapled's launcher, the one the Preview button opens: the token
485
+ is Mapled's, the CLI only passes it on. `--locale` opens the page in that
486
+ language (§28.4: Mapled builds the address from the manifest's `locales`;
487
+ a language the project doesn't have is refused by Mapled). `--open` hands
488
+ it to the browser instead of printing it (the link works once). */
485
489
  export async function preview(ctx, flags, words) {
486
490
  const [collection, record, ...rest] = words;
487
491
  if (rest.length > 0) {
488
492
  throw new CliError("Name a collection and, optionally, one record's slug: `mapled preview articles hello-world`.", 2);
489
493
  }
494
+ const locale = stringFlag(flags, "locale");
495
+ if (locale !== undefined && !/^[A-Za-z0-9_-]{1,40}$/.test(locale)) {
496
+ throw new CliError("--locale takes a language code such as ru or pt-BR.", 2);
497
+ }
490
498
  const { session } = await openSession(ctx, flags);
491
499
  let answer;
492
500
  try {
493
501
  answer = await session.send("POST", "/v1/agent/preview", {
494
502
  ...(collection ? { collection } : {}),
495
503
  ...(record ? { record } : {}),
504
+ ...(locale ? { locale } : {}),
496
505
  });
497
506
  }
498
507
  catch (err) {
@@ -507,15 +516,22 @@ export async function preview(ctx, flags, words) {
507
516
  if (answer.status === "not_set_up") {
508
517
  throw new CliError(`${clean(answer.origin)} has no preview route — mount createPreviewHandler from "@mapled/next/server" at /api/mapled/preview and redeploy.`);
509
518
  }
519
+ if (answer.status === "no_route") {
520
+ // a site with its content baked in at build time has no page for a record it wasn't built with (§22.7 п. 3)
521
+ const where = answer.path ? `at ${clean(answer.path, 500)}` : "for this record";
522
+ throw new CliError(`${clean(answer.origin)} has no page ${where} — if the record is new, its page appears once the site is built with it. Then try again.`);
523
+ }
510
524
  // the link is printed as it came (masking would drop the token): printable ASCII only
511
525
  if (typeof answer.url !== "string" || !/^https?:\/\/[!-~]+$/.test(answer.url)) {
512
526
  throw new CliError("Mapled answered with a link this CLI can't print. Update @mapled/cli and try again.");
513
527
  }
528
+ // the language Mapled opened the page in (the answer names it only when one was asked)
529
+ const inLanguage = typeof answer.locale === "string" && /^[A-Za-z0-9_-]{1,40}$/.test(answer.locale) ? answer.locale : null;
514
530
  if (flags.json) {
515
- ctx.out(JSON.stringify({ url: answer.url, path: answer.path, expiresAt: answer.expiresAt ?? null }, null, 2));
531
+ ctx.out(JSON.stringify({ url: answer.url, path: answer.path, expiresAt: answer.expiresAt ?? null, ...(inLanguage ? { locale: inLanguage } : {}) }, null, 2));
516
532
  return;
517
533
  }
518
- const where = answer.path ? clean(answer.path, 500) : "the site's home page";
534
+ const where = (answer.path ? clean(answer.path, 500) : "the site's home page") + (inLanguage ? ` in ${inLanguage}` : "");
519
535
  const lifetime = linkLifetime(answer.expiresAt);
520
536
  const project = clean(session.conn.projectName, 120);
521
537
  if (flags.open && (await ctx.openBrowser(answer.url))) {
@@ -621,6 +637,8 @@ export async function scan(ctx, flags) {
621
637
  parts.push(`${merged.unchanged.length} unchanged`);
622
638
  if (merged.capabilitiesChanged)
623
639
  parts.push("capabilities updated");
640
+ if (merged.localesChanged)
641
+ parts.push("routes by language updated");
624
642
  ctx.out(`Wrote ${MANIFEST_FILE} — ${manifestSummary(merged.manifest)}${parts.length > 0 && existing ? ` (${parts.join(", ")})` : ""}.${keptNote}${droppedNote}`);
625
643
  ctx.out("Next: `mapled bindings push`.");
626
644
  }
@@ -636,10 +654,12 @@ export async function scan(ctx, flags) {
636
654
  parts.push(`${merged.unchanged.length} unchanged`);
637
655
  if (merged.capabilitiesChanged)
638
656
  parts.push("capabilities changed");
657
+ if (merged.localesChanged)
658
+ parts.push("routes by language changed");
639
659
  if (merged.kept.length > 0)
640
660
  parts.push(`${merged.kept.length} not found in the code (${merged.kept.slice(0, 5).join(", ")}${merged.kept.length > 5 ? ", …" : ""})`);
641
661
  ctx.out(`Compared with ${MANIFEST_FILE}: ${parts.join(", ")}.`);
642
- if (merged.added.length > 0 || merged.changed.length > 0 || merged.capabilitiesChanged) {
662
+ if (merged.added.length > 0 || merged.changed.length > 0 || merged.capabilitiesChanged || merged.localesChanged) {
643
663
  ctx.out(`Run \`mapled scan --write\` to update ${MANIFEST_FILE}, then \`mapled bindings push\`.`);
644
664
  }
645
665
  else {
@@ -710,10 +730,12 @@ export async function doctor(ctx, flags) {
710
730
  // plain HTML installs nothing: the runtime comes with a script tag
711
731
  checks.push(framework === "plain-html" ? dr.checkScriptTag(await dr.findScriptTag(dir, status?.site?.url), status?.sdk[sdk]) : dr.checkSdk(await dr.installedVersion(dir, sdk), status?.sdk[sdk], sdk));
712
732
  checks.push(dr.checkCli(ctx.version, status?.sdk["@mapled/cli"]));
713
- checks.push(dr.checkRoute("revalidate", await dr.findRoute(dir, "revalidate"), framework));
733
+ // the live facts first: the revalidate route's answer matters once a page listens (#435)
734
+ const live = !framework || framework === "nextjs" ? await findLive(dir) : null;
735
+ checks.push(dr.checkRoute("revalidate", await dr.findRoute(dir, "revalidate"), framework, (live?.listeners.length ?? 0) > 0));
714
736
  checks.push(dr.checkRoute("preview", await dr.findRoute(dir, "preview"), framework));
715
737
  // the code is read only where the check applies: a Next.js site (or one doctor can't place)
716
- checks.push(dr.checkLive(!framework || framework === "nextjs" ? await findLive(dir) : null, framework));
738
+ checks.push(dr.checkLive(live, framework));
717
739
  if (status) {
718
740
  checks.push(dr.checkReads(status.delivery.lastReadAt));
719
741
  checks.push(dr.checkWebhook(status.webhook, { local: browser, remote: status.site }));
package/dist/doctor.d.ts CHANGED
@@ -153,9 +153,12 @@ export declare function detectFramework(dir: string): Promise<string | undefined
153
153
  export declare function compareVersions(a: string, b: string): number;
154
154
  export type RouteKind = "revalidate" | "preview";
155
155
  export declare const ROUTE_HANDLER: Record<RouteKind, string>;
156
+ /** `cacheMark` and `fetchCache` — the revalidate route only (revalidateRouteFacts). */
156
157
  export type RouteFile = {
157
158
  file: string;
158
159
  usesAdapter: boolean;
160
+ cacheMark?: boolean;
161
+ fetchCache?: boolean;
159
162
  };
160
163
  export declare function findRoute(dir: string, kind: RouteKind): Promise<RouteFile | null>;
161
164
  /** GET the site's preview route without a token — the adapter answers 400. */
@@ -180,7 +183,9 @@ export declare function checkEnv(names: Map<string, string>, env: NodeJS.Process
180
183
  export declare function checkSecrets(facts: GitFacts): Check;
181
184
  export declare function checkSdk(pkg: PackageVersion, latest: string | undefined, name?: string): Check;
182
185
  export declare function checkCli(current: string, latest: string | undefined): Check;
183
- export declare function checkRoute(kind: RouteKind, found: RouteFile | null, framework: string | undefined): Check;
186
+ /** `listens`: a page follows Publish (<MapledLive /> or watchRelease) — then
187
+ the revalidate route's answer is what the release route trusts (#435). */
188
+ export declare function checkRoute(kind: RouteKind, found: RouteFile | null, framework: string | undefined, listens?: boolean): Check;
184
189
  /** Live updates (§18.8): whether open tabs follow Publish, and when the
185
190
  code listens, whether a route answers it. `facts` is null when the
186
191
  code wasn't read (a stack this check doesn't cover). */
package/dist/doctor.js CHANGED
@@ -5,7 +5,7 @@ import path from "node:path";
5
5
  import { promisify } from "node:util";
6
6
  import { parseDocument } from "./html5.js";
7
7
  import { integrationHash } from "./integrationHash.js";
8
- import { BASE_PATH_SINCE, fullAddress, liveState, pagesRouteFileFor, RELEASE_ROUTE, routeFileFor, runsIn } from "./live.js";
8
+ import { BASE_PATH_SINCE, fullAddress, liveState, pagesRouteFileFor, RELEASE_ROUTE, revalidateRouteFacts, routeFileFor, runsIn } from "./live.js";
9
9
  import { compareManifests, manifestSummary, MANIFEST_FILE } from "./manifest.js";
10
10
  import { SCHEMA_FILE } from "./pin.js";
11
11
  import { htmlPage } from "./scanHtml.js";
@@ -468,7 +468,8 @@ export async function findRoute(dir, kind) {
468
468
  catch {
469
469
  continue;
470
470
  }
471
- return { file: rel, usesAdapter: text.includes(ROUTE_HANDLER[kind]) };
471
+ const usesAdapter = text.includes(ROUTE_HANDLER[kind]);
472
+ return kind === "revalidate" ? { file: rel, usesAdapter, ...revalidateRouteFacts(text) } : { file: rel, usesAdapter };
472
473
  }
473
474
  }
474
475
  return null;
@@ -639,7 +640,9 @@ export function checkCli(current, latest) {
639
640
  }
640
641
  return { key: "cli", label, status: "passed", detail: latest ? `${current} (current)` : current };
641
642
  }
642
- export function checkRoute(kind, found, framework) {
643
+ /** `listens`: a page follows Publish (<MapledLive /> or watchRelease) — then
644
+ the revalidate route's answer is what the release route trusts (#435). */
645
+ export function checkRoute(kind, found, framework, listens = false) {
643
646
  const key = `route_${kind}`;
644
647
  const label = kind === "revalidate" ? "Revalidation route" : "Preview route";
645
648
  const handler = ROUTE_HANDLER[kind];
@@ -671,6 +674,22 @@ export function checkRoute(kind, found, framework) {
671
674
  detail: `app/api/mapled/preview/route.ts is missing — mount ${handler} from "@mapled/next/server" so Preview from Mapled works.`,
672
675
  };
673
676
  }
677
+ if (kind === "revalidate" && listens && !found.usesAdapter && !found.cacheMark) {
678
+ return {
679
+ key,
680
+ label,
681
+ status: "warning",
682
+ detail: `${found.file} doesn't use ${handler} and doesn't answer with cacheMark() — the release route then names no release, so open tabs don't follow Publish. Use ${handler} from "@mapled/next/server", or answer with the header x-mapled-cache: await cacheMark().`,
683
+ };
684
+ }
685
+ if (kind === "revalidate" && listens && found.fetchCache) {
686
+ return {
687
+ key,
688
+ label,
689
+ status: "warning",
690
+ detail: `${found.file} may set fetchCache to something other than "auto" — with "force-no-store" every answer names a new cache mark, so the release route may name no release and open tabs don't follow Publish. Leave fetchCache out of the route.`,
691
+ };
692
+ }
674
693
  if (!found.usesAdapter) {
675
694
  return { key, label, status: "warning", detail: `${found.file} exists but doesn't use ${handler} from "@mapled/next/server".` };
676
695
  }
@@ -1021,7 +1040,7 @@ export function checkManifestFile(local, remote) {
1021
1040
  if (remote === null)
1022
1041
  return { key: "manifest", label, status: "warning", detail: `${MANIFEST_FILE} isn't pushed yet — run \`mapled bindings push\`.` };
1023
1042
  const diff = compareManifests(local.manifest, remote.manifest);
1024
- if (diff.same && !diff.capabilitiesDiffer) {
1043
+ if (diff.same && !diff.capabilitiesDiffer && !diff.localesDiffer) {
1025
1044
  return { key: "manifest", label, status: "passed", detail: `${MANIFEST_FILE} is pushed as manifest v${remote.version} (${manifestSummary(local.manifest)})` };
1026
1045
  }
1027
1046
  const parts = [];
@@ -1034,6 +1053,8 @@ export function checkManifestFile(local, remote) {
1034
1053
  // capabilities aren't in the integration hash, so this is the line that notices them
1035
1054
  if (diff.capabilitiesDiffer)
1036
1055
  parts.push("capabilities changed");
1056
+ if (diff.localesDiffer)
1057
+ parts.push("routes by language changed");
1037
1058
  return {
1038
1059
  key: "manifest",
1039
1060
  label,
package/dist/html5.d.ts CHANGED
@@ -18,11 +18,21 @@ import { type Token, type TreeAdapter, type TreeAdapterTypeMap } from "parse5";
18
18
  before a <table>: indexOf), the adoption agency (a misnested </b>:
19
19
  splice from the front) and repeated <html>/<body> tags (every
20
20
  attribute so far) walk them — 160,000 "a<br>" in a table: 11 s.
21
- The worst page within the cap is the depth times its start tags. */
21
+ The worst page within the cap is the depth times its start tags.
22
+
23
+ parse5 can't read a numeric character reference of 309 or more decimal
24
+ digits, or of 256 or more hex ones, at all: BadReference. */
22
25
  /** How deep elements may be open at once — Chrome's own limit of the tree it builds. */
23
26
  export declare const MAX_OPEN = 512;
24
27
  export declare class TooDeep extends Error {
25
28
  }
29
+ /** A numeric character reference parse5 can't read: its decoder (entities)
30
+ multiplies by 10 or 16 to the power of the digits — Infinity past 308
31
+ decimal or 255 hex ones — and 0 × Infinity is NaN, on which parse5
32
+ throws RangeError. Browsers read it as a character. The same guard is
33
+ in the preview proxy's reader (apps/api/src/lib/previewProxyHtml.ts). */
34
+ export declare class BadReference extends Error {
35
+ }
26
36
  export type Html5Attr = Token.Attribute;
27
37
  /** A node of the tree: an element (tagName), text, a comment, a doctype,
28
38
  the document or a template's content — children linked both ways. Its
@@ -61,6 +71,7 @@ type Map_ = TreeAdapterTypeMap<Html5Node, Html5Node, Html5Node, Html5Node, Html5
61
71
  /** parse5's default tree adapter, method for method, on linked children. */
62
72
  export declare const linked: TreeAdapter<Map_>;
63
73
  /** The document, with scripting on — as the runtime sees it (a <noscript>
64
- is text). Throws TooDeep past MAX_OPEN open elements. */
74
+ is text). Throws TooDeep past MAX_OPEN open elements, BadReference at a
75
+ numeric character reference parse5 can't read. */
65
76
  export declare function parseDocument(text: string): Html5Node;
66
77
  export {};
package/dist/html5.js CHANGED
@@ -7853,6 +7853,9 @@ var VOID_ELEMENTS = /* @__PURE__ */ new Set([
7853
7853
  var MAX_OPEN = 512;
7854
7854
  var TooDeep = class extends Error {
7855
7855
  };
7856
+ var BadReference = class extends Error {
7857
+ };
7858
+ var isCodePoint = (cp) => Number.isInteger(cp) && cp >= 0 && cp <= 1114111;
7856
7859
  var Html5Node = class {
7857
7860
  constructor(nodeName, extra = {}) {
7858
7861
  this.nodeName = nodeName;
@@ -7991,6 +7994,11 @@ var Guarded = class extends Parser {
7991
7994
  names.add(this.currentAttr.name);
7992
7995
  tag.attrs.push(this.currentAttr);
7993
7996
  };
7997
+ const reference = tokenizer._flushCodePointConsumedAsCharacterReference;
7998
+ tokenizer._flushCodePointConsumedAsCharacterReference = function(cp) {
7999
+ if (!isCodePoint(cp)) throw new BadReference();
8000
+ reference.call(this, cp);
8001
+ };
7994
8002
  }
7995
8003
  onItemPush(...args) {
7996
8004
  if (this.openElements.stackTop >= MAX_OPEN) throw new TooDeep();
@@ -8001,6 +8009,7 @@ function parseDocument(text) {
8001
8009
  return Guarded.parse(text, { scriptingEnabled: true, treeAdapter: linked });
8002
8010
  }
8003
8011
  export {
8012
+ BadReference,
8004
8013
  Html5Node,
8005
8014
  MAX_OPEN,
8006
8015
  TooDeep,
package/dist/index.js CHANGED
@@ -28,7 +28,7 @@ Usage
28
28
  mapled forms list [--json] List the project's forms and where the site posts to
29
29
  mapled doctor [--json] Check the integration end to end
30
30
  mapled integration report [--json] [--verify] Show the last setup run's verification checklist and report
31
- mapled preview [<collection> [<slug>]] [--open] Print a one-time Preview link that shows the drafts on the site
31
+ mapled preview [<collection> [<slug>]] [--locale <code>] [--open] Print a one-time Preview link that shows the drafts on the site
32
32
 
33
33
  Options
34
34
  --version, -v Print the version
@@ -1,7 +1,7 @@
1
1
  /** The integration hash (§21.4): one marker over everything the site's
2
2
  integration was written against — the schema (its hash, the twelve hex
3
3
  characters in mapled-types.ts and mapled/schema.json) and the site
4
- manifest (bindings, pages, framework, notes). Mapled records it on
4
+ manifest (bindings, pages, framework, notes, routes by language). Mapled records it on
5
5
  every manifest push and computes it again on read; the repository
6
6
  computes the same value from mapled/schema.json and
7
7
  mapled/manifest.json, and `mapled doctor` compares the two in one
@@ -31,5 +31,10 @@ export type HashedManifest = {
31
31
  }[] | null;
32
32
  bindings: HashedBinding[];
33
33
  notes?: string[] | null;
34
+ /** routes by language (§28.4); a manifest without it hashes as before */
35
+ locales?: {
36
+ routing: string;
37
+ param?: string | null;
38
+ } | null;
34
39
  };
35
40
  export declare function integrationHash(schemaHash: string, manifest: HashedManifest): string;
@@ -21,6 +21,8 @@ export function integrationHash(schemaHash, manifest) {
21
21
  }))
22
22
  .sort((a, b) => byText(a.key, b.key)),
23
23
  notes: manifest.notes ?? [],
24
+ // only when declared: the hashes of every manifest without it stay what they were
25
+ ...(manifest.locales ? { locales: { routing: manifest.locales.routing, param: manifest.locales.param || null } } : {}),
24
26
  };
25
27
  return createHash("sha256").update(JSON.stringify(projection)).digest("hex").slice(0, 12);
26
28
  }
package/dist/live.d.ts CHANGED
@@ -208,6 +208,16 @@ export declare function liveInFile(file: string, text: string, router?: "app" |
208
208
  segment?: SegmentSetting;
209
209
  runtime?: string | null;
210
210
  };
211
+ /** What the revalidate (webhook) route does that live updates depend on
212
+ (§18.8, #435): whether it answers with the cache's mark (`cacheMark()`,
213
+ which createRevalidateHandler does for you), and whether it may export
214
+ `fetchCache` as anything but "auto" — "force-no-store" makes every
215
+ answer name a new mark, so the release route may name no release.
216
+ Read the way the release route's segment config is read. */
217
+ export declare function revalidateRouteFacts(text: string): {
218
+ cacheMark: boolean;
219
+ fetchCache: boolean;
220
+ };
211
221
  /** The route segment settings that change how Next serves the release
212
222
  route or reads what the handler reads, in the order doctor names them:
213
223
  `dynamic` can make it a static copy built without the tab's request,
package/dist/live.js CHANGED
@@ -945,6 +945,19 @@ export function liveInFile(file, text, router = "app") {
945
945
  const segment = sets("dynamic", SEGMENT_KEEPS.dynamic) ? "dynamic" : forced() ? "force-dynamic" : sets("fetchCache", SEGMENT_KEEPS.fetchCache) ? "fetchCache" : undefined;
946
946
  return { listeners, handler: releaseHandlerOf(tokens, APP_HANDLER(names), "GET", read), params, ...(segment ? { segment } : {}) };
947
947
  }
948
+ /** What the revalidate (webhook) route does that live updates depend on
949
+ (§18.8, #435): whether it answers with the cache's mark (`cacheMark()`,
950
+ which createRevalidateHandler does for you), and whether it may export
951
+ `fetchCache` as anything but "auto" — "force-no-store" makes every
952
+ answer name a new mark, so the release route may name no release.
953
+ Read the way the release route's segment config is read. */
954
+ export function revalidateRouteFacts(text) {
955
+ const tokens = tokenize(text);
956
+ const cacheMark = tokens.some((t, k) => t.kind === "ident" && t.value === "cacheMark" && isPunct(tokens[k + 1], "("));
957
+ const listing = tokens.some((u, k) => isWord(u, "exports") || (isWord(u, "export") && isPunct(tokens[k + 1], "*")));
958
+ const fetchCache = (listing || tokens.some((u) => isName(u, "fetchCache"))) && setsExport(tokens, readingOf(tokens).b.after, "fetchCache", SEGMENT_KEEPS.fetchCache);
959
+ return { cacheMark, fetchCache };
960
+ }
948
961
  /** Whether every release handler the file makes keeps a window of its own
949
962
  for the pointer's read (keepsWindow), with the factory only ever called
950
963
  where it's named: `const make = createReleaseHandler`, a namespace
@@ -30,6 +30,18 @@ export type ManifestCapabilities = {
30
30
  realtime?: boolean;
31
31
  releaseRoute?: string;
32
32
  };
33
+ /** How the site's routes carry the language (§28.4): `prefix` — every
34
+ language under its own (`/en/…`, `/ru/…`), `prefix-except-default` —
35
+ the default language at the root, the others under theirs; `param`
36
+ names the dynamic segment that holds it (`/[locale]/blog/[slug]`),
37
+ without one Mapled puts the language before the page. Part of the
38
+ integration hash: Mapled builds each language's address of a record
39
+ from it (the editor's routes, Preview). */
40
+ export type ManifestLocales = {
41
+ routing: "prefix" | "prefix-except-default";
42
+ param?: string;
43
+ };
44
+ export declare const LOCALE_ROUTINGS: readonly ["prefix", "prefix-except-default"];
33
45
  export type Manifest = {
34
46
  framework?: string;
35
47
  integrationMode?: string;
@@ -37,6 +49,7 @@ export type Manifest = {
37
49
  bindings: ManifestBinding[];
38
50
  notes?: string[];
39
51
  capabilities?: ManifestCapabilities;
52
+ locales?: ManifestLocales;
40
53
  };
41
54
  export declare const KEY_PATTERN: RegExp;
42
55
  export declare const LIMITS: {
@@ -93,6 +106,8 @@ export declare function readManifestFile(dir: string): Promise<{
93
106
  the order they came in. */
94
107
  export declare function writeManifestFile(dir: string, manifest: Manifest): Promise<string>;
95
108
  export declare function orderManifest(manifest: Manifest): Manifest;
109
+ /** Whether two manifests route languages the same way (missing on both is the same). */
110
+ export declare function sameLocales(a: ManifestLocales | undefined, b: ManifestLocales | undefined): boolean;
96
111
  /** Whether two manifests declare the same capabilities (missing and empty are the same). */
97
112
  export declare function sameCapabilities(a: ManifestCapabilities | undefined, b: ManifestCapabilities | undefined): boolean;
98
113
  export declare function orderBinding(b: ManifestBinding): ManifestBinding;
@@ -100,12 +115,14 @@ export declare function plural(n: number, word: string, pluralWord?: string): st
100
115
  export declare function manifestSummary(manifest: Manifest): string;
101
116
  /** Bindings that mean the same thing to Mapled (what a push stores). */
102
117
  export declare function sameBinding(a: ManifestBinding, b: ManifestBinding): boolean;
103
- /** `same` is about the bindings; `capabilitiesDiffer` says whether the capabilities do. */
118
+ /** `same` is about the bindings; `capabilitiesDiffer` says whether the
119
+ capabilities do, `localesDiffer` whether the routes by language do. */
104
120
  export type ManifestComparison = {
105
121
  same: boolean;
106
122
  onlyLocal: string[];
107
123
  onlyRemote: string[];
108
124
  changed: string[];
109
125
  capabilitiesDiffer: boolean;
126
+ localesDiffer: boolean;
110
127
  };
111
128
  export declare function compareManifests(local: Manifest, remote: Manifest): ManifestComparison;
package/dist/manifest.js CHANGED
@@ -22,6 +22,7 @@ export const BINDING_TARGETS = [
22
22
  "form_field",
23
23
  "other",
24
24
  ];
25
+ export const LOCALE_ROUTINGS = ["prefix", "prefix-except-default"];
25
26
  export const KEY_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:/\[\]-]{0,119}$/;
26
27
  export const LIMITS = { bindings: 500, pages: 200, notes: 20 };
27
28
  /** Field types a target renders without a transform — the API's table
@@ -91,7 +92,8 @@ export function typeLabel(type) {
91
92
  }
92
93
  const isObject = (v) => !!v && typeof v === "object" && !Array.isArray(v);
93
94
  const isText = (v, max, min = 1) => typeof v === "string" && v.length >= min && v.length <= max;
94
- const TOP_KEYS = new Set(["framework", "integrationMode", "pages", "bindings", "notes", "capabilities"]);
95
+ const TOP_KEYS = new Set(["framework", "integrationMode", "pages", "bindings", "notes", "capabilities", "locales"]);
96
+ const LOCALE_PARAM = /^[A-Za-z0-9_-]{1,60}$/;
95
97
  export const CAPABILITY_KEYS = ["realtime", "releaseRoute"];
96
98
  const BINDING_KEYS = new Set(["key", "page", "component", "file", "collection", "field", "target", "required"]);
97
99
  /** Parses the file's text into a manifest with only the known fields,
@@ -271,6 +273,31 @@ export function parseManifest(raw, file) {
271
273
  manifest.capabilities = kept;
272
274
  }
273
275
  }
276
+ if (data.locales !== undefined) {
277
+ const loc = data.locales;
278
+ if (!isObject(loc)) {
279
+ error("locales", `"locales" must be an object — { routing, param }.`);
280
+ }
281
+ else {
282
+ let ok = true;
283
+ for (const key of Object.keys(loc)) {
284
+ if (key !== "routing" && key !== "param") {
285
+ error(`locales.${key}`, `locales.${key} isn't part of the manifest — only routing and param; Mapled refuses the push.`);
286
+ ok = false;
287
+ }
288
+ }
289
+ if (!LOCALE_ROUTINGS.includes(loc.routing)) {
290
+ error("locales.routing", `locales.routing must be "prefix" (every language under its own, /en/…) or "prefix-except-default" (the default language at the root).`);
291
+ ok = false;
292
+ }
293
+ if (loc.param !== undefined && !(typeof loc.param === "string" && LOCALE_PARAM.test(loc.param))) {
294
+ error("locales.param", `locales.param must name the dynamic segment that carries the language, e.g. "locale" for /[locale]/blog/[slug].`);
295
+ ok = false;
296
+ }
297
+ if (ok)
298
+ manifest.locales = { routing: loc.routing, ...(typeof loc.param === "string" ? { param: loc.param } : {}) };
299
+ }
300
+ }
274
301
  return { manifest, problems };
275
302
  }
276
303
  export function indexSchema(schema) {
@@ -302,6 +329,14 @@ export async function checkManifest(manifest, opts) {
302
329
  const warn = (p, message) => problems.push({ level: "warning", path: p, message });
303
330
  const index = opts.schema ? indexSchema(opts.schema) : null;
304
331
  const routes = new Set((manifest.pages ?? []).map((p) => p.route));
332
+ // routes by language (§28.4): a segment the manifest names must be on some page
333
+ if (manifest.locales?.param !== undefined) {
334
+ const marker = `[${manifest.locales.param}]`;
335
+ const all = [...routes, ...manifest.bindings.map((b) => b.page)];
336
+ if (all.length > 0 && !all.some((r) => r.split("?")[0].split("/").includes(marker))) {
337
+ warn("locales.param", `locales.param names "${marker}", but no page route has that segment — the language then goes nowhere; leave param out for Mapled to put the language before the page.`);
338
+ }
339
+ }
305
340
  for (const [i, p] of (manifest.pages ?? []).entries()) {
306
341
  if (p.file && !(await opts.fileExists(p.file))) {
307
342
  warn(`pages[${i}].file`, `pages[${i}] (${p.route}): ${p.file} doesn't exist in the repository.`);
@@ -381,8 +416,13 @@ export function orderManifest(manifest) {
381
416
  bindings: manifest.bindings.map(orderBinding),
382
417
  ...(manifest.notes && manifest.notes.length > 0 ? { notes: [...manifest.notes] } : {}),
383
418
  ...(manifest.capabilities ? { capabilities: orderCapabilities(manifest.capabilities) } : {}),
419
+ ...(manifest.locales ? { locales: { routing: manifest.locales.routing, ...(manifest.locales.param !== undefined ? { param: manifest.locales.param } : {}) } } : {}),
384
420
  };
385
421
  }
422
+ /** Whether two manifests route languages the same way (missing on both is the same). */
423
+ export function sameLocales(a, b) {
424
+ return (a?.routing ?? null) === (b?.routing ?? null) && (a?.param ?? null) === (b?.param ?? null);
425
+ }
386
426
  function orderCapabilities(c) {
387
427
  return {
388
428
  ...(c.realtime !== undefined ? { realtime: c.realtime } : {}),
@@ -434,5 +474,6 @@ export function compareManifests(local, remote) {
434
474
  onlyRemote,
435
475
  changed,
436
476
  capabilitiesDiffer: !sameCapabilities(local.capabilities, remote.capabilities),
477
+ localesDiffer: !sameLocales(local.locales, remote.locales),
437
478
  };
438
479
  }
package/dist/report.d.ts CHANGED
@@ -114,6 +114,7 @@ export type PreviewAnswer = {
114
114
  url: string;
115
115
  expiresAt?: string;
116
116
  path: string | null;
117
+ locale?: string;
117
118
  } | {
118
119
  status: "not_deployed";
119
120
  origin: string;
@@ -122,6 +123,10 @@ export type PreviewAnswer = {
122
123
  status: "not_set_up";
123
124
  origin: string;
124
125
  path: string | null;
126
+ } | {
127
+ status: "no_route";
128
+ origin: string;
129
+ path: string | null;
125
130
  };
126
131
  /** "5 minutes" until the link stops working, or null when unknown. */
127
132
  export declare function linkLifetime(expiresAt: string | undefined, now?: number): string | null;
package/dist/scan.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type * as TS from "typescript";
2
- import { type Manifest, type ManifestBinding, type ManifestCapabilities, type ManifestPage } from "./manifest.js";
2
+ import { type Manifest, type ManifestBinding, type ManifestCapabilities, type ManifestLocales, type ManifestPage } from "./manifest.js";
3
3
  import type { Schema } from "./schema.js";
4
4
  /** `mapled scan` (§19.5, §21.4 — the standalone scanner of wave 2): reads
5
5
  the site's source and writes down what the code reads from Mapled —
@@ -39,7 +39,14 @@ export type ScanResult = {
39
39
  notes: string[];
40
40
  /** what the code says the site can do (live updates of a Next.js site); undefined keeps the manifest's */
41
41
  capabilities?: ManifestCapabilities;
42
+ /** how the routes carry the language (§28.4), when a leading dynamic segment names it; undefined keeps the manifest's */
43
+ locales?: ManifestLocales;
42
44
  };
45
+ /** Routes by language from the file conventions (§28.4): a dynamic segment
46
+ named for the language at the front of a route (`/[locale]/…`,
47
+ `/[lang]/…`) — every language then lives under its own prefix, and
48
+ the segment is the manifest's `param`. Null when no route starts so. */
49
+ export declare function detectLocales(routes: Iterable<string>): ManifestLocales | null;
43
50
  export declare const SDK_METHODS: Set<string>;
44
51
  /** The repository's TypeScript, when it has one; the CLI ships none. */
45
52
  export declare function loadTypeScript(dir: string): TypeScriptModule | null;
@@ -90,6 +97,8 @@ export type MergeResult = {
90
97
  dropped: string[];
91
98
  /** the scan changed what an existing manifest says the site can do */
92
99
  capabilitiesChanged: boolean;
100
+ /** the scan changed how an existing manifest routes languages */
101
+ localesChanged: boolean;
93
102
  };
94
103
  /** The scan's bindings replace the ones with the same key; bindings the
95
104
  scanner can't see (an agent's, hand-written) stay unless pruned. */
package/dist/scan.js CHANGED
@@ -1,7 +1,22 @@
1
1
  import { createRequire } from "node:module";
2
2
  import { readdir, readFile } from "node:fs/promises";
3
3
  import path from "node:path";
4
- import { sameCapabilities, targetFor } from "./manifest.js";
4
+ import { sameCapabilities, sameLocales, targetFor } from "./manifest.js";
5
+ /** The segment names a site gives the language at the front of its routes. */
6
+ const LOCALE_SEGMENTS = new Set(["locale", "lang", "lng", "language"]);
7
+ /** Routes by language from the file conventions (§28.4): a dynamic segment
8
+ named for the language at the front of a route (`/[locale]/…`,
9
+ `/[lang]/…`) — every language then lives under its own prefix, and
10
+ the segment is the manifest's `param`. Null when no route starts so. */
11
+ export function detectLocales(routes) {
12
+ for (const route of routes) {
13
+ const first = route.split("/")[1];
14
+ const m = first ? /^\[([A-Za-z0-9_-]{1,60})\]$/.exec(first) : null;
15
+ if (m && LOCALE_SEGMENTS.has(m[1].toLowerCase()))
16
+ return { routing: "prefix", param: m[1] };
17
+ }
18
+ return null;
19
+ }
5
20
  const SKIP_DIRS = new Set([
6
21
  "node_modules",
7
22
  ".git",
@@ -1398,6 +1413,7 @@ export async function scanRepository(dir, opts) {
1398
1413
  if (!opts.ts) {
1399
1414
  notes.push("TypeScript isn't installed in this repository, so fields weren't traced (collections only) — npm install -D typescript and scan again.");
1400
1415
  }
1416
+ const locales = detectLocales(routes.pages.values());
1401
1417
  return {
1402
1418
  framework,
1403
1419
  parser: opts.ts ? "typescript" : "tokens",
@@ -1405,6 +1421,7 @@ export async function scanRepository(dir, opts) {
1405
1421
  pages: uniquePages,
1406
1422
  bindings: sortedBindings,
1407
1423
  notes,
1424
+ ...(locales ? { locales } : {}),
1408
1425
  };
1409
1426
  }
1410
1427
  /** The scan's bindings replace the ones with the same key; bindings the
@@ -1451,7 +1468,10 @@ export function mergeManifest(existing, scan, opts) {
1451
1468
  bindings: out,
1452
1469
  ...(existing?.notes && existing.notes.length > 0 ? { notes: existing.notes } : {}),
1453
1470
  ...(scan.capabilities ? { capabilities: scan.capabilities } : existing?.capabilities ? { capabilities: existing.capabilities } : {}),
1471
+ // the scan's routes by language, else the manifest's (written by hand or by the agent)
1472
+ ...(scan.locales ? { locales: scan.locales } : existing?.locales ? { locales: existing.locales } : {}),
1454
1473
  };
1455
1474
  const capabilitiesChanged = existing !== null && !sameCapabilities(existing.capabilities, manifest.capabilities);
1456
- return { manifest, added, changed, unchanged, kept, dropped, capabilitiesChanged };
1475
+ const localesChanged = existing !== null && !sameLocales(existing.locales, manifest.locales);
1476
+ return { manifest, added, changed, unchanged, kept, dropped, capabilitiesChanged, localesChanged };
1457
1477
  }
@@ -20,7 +20,8 @@ export type HtmlElement = {
20
20
  };
21
21
  /** The elements of a page and their attributes — parse5's tree (html5.ts),
22
22
  a <template>'s children being its content. Built without recursion:
23
- a page nests up to MAX_OPEN elements, deeper throws TooDeep. */
23
+ a page nests up to MAX_OPEN elements, deeper throws TooDeep; a
24
+ numeric character reference parse5 can't read throws BadReference. */
24
25
  export declare function parseHtml(text: string): HtmlElement;
25
26
  /** Where a file is served and the key its bindings start with:
26
27
  index.html → "/" (index), blog/index.html → "/blog/" (blog/index),
package/dist/scanHtml.js CHANGED
@@ -1,13 +1,14 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { readdir, readFile } from "node:fs/promises";
3
3
  import path from "node:path";
4
- import { MAX_OPEN, parseDocument, TooDeep } from "./html5.js";
4
+ import { BadReference, MAX_OPEN, parseDocument, TooDeep } from "./html5.js";
5
5
  import { targetFor } from "./manifest.js";
6
6
  const HTML_NS = "http://www.w3.org/1999/xhtml";
7
7
  const MAX_DEPTH = MAX_OPEN;
8
8
  /** The elements of a page and their attributes — parse5's tree (html5.ts),
9
9
  a <template>'s children being its content. Built without recursion:
10
- a page nests up to MAX_OPEN elements, deeper throws TooDeep. */
10
+ a page nests up to MAX_OPEN elements, deeper throws TooDeep; a
11
+ numeric character reference parse5 can't read throws BadReference. */
11
12
  export function parseHtml(text) {
12
13
  const root = { name: "#document", attrs: new Map(), children: [] };
13
14
  const pending = [[parseDocument(text), root]];
@@ -148,6 +149,7 @@ export async function scanHtml(dir, opts) {
148
149
  const noTemplate = [];
149
150
  const unusableRoutes = [];
150
151
  const tooDeep = [];
152
+ const badReferences = [];
151
153
  const notGroups = [];
152
154
  const seenRoutes = new Set();
153
155
  const pageKeys = new Set();
@@ -170,9 +172,12 @@ export async function scanHtml(dir, opts) {
170
172
  doc = parseHtml(text);
171
173
  }
172
174
  catch (e) {
173
- if (!(e instanceof TooDeep))
175
+ if (e instanceof TooDeep)
176
+ tooDeep.push(file);
177
+ else if (e instanceof BadReference)
178
+ badReferences.push(file);
179
+ else
174
180
  throw e;
175
- tooDeep.push(file);
176
181
  continue;
177
182
  }
178
183
  seenRoutes.add(served);
@@ -422,6 +427,8 @@ export async function scanHtml(dir, opts) {
422
427
  notes.push(`A list or an each without a <template> isn't filled: ${list(noTemplate)}.`);
423
428
  if (tooDeep.length > 0)
424
429
  notes.push(`Elements nest deeper than ${MAX_OPEN} levels, so the scan didn't read ${count(tooDeep) === 1 ? "this page" : "these pages"}: ${list(tooDeep)}.`);
430
+ if (badReferences.length > 0)
431
+ notes.push(`A numeric character reference (&#…;) is too long to read, so the scan didn't read ${count(badReferences) === 1 ? "this page" : "these pages"}: ${list(badReferences)}.`);
425
432
  if (unusableRoutes.length > 0)
426
433
  notes.push(`Preview can't open ${count(unusableRoutes) === 1 ? "this address" : "these addresses"} — rename the file or the slug parameter: ${list(unusableRoutes)}.`);
427
434
  // keys within the manifest's 120 characters, and still one per binding
package/dist/types.js CHANGED
@@ -153,7 +153,9 @@ function renderedField(field, owner, names, extras) {
153
153
  if (field.type === "computed" && field.computed) {
154
154
  return { required: false, rendered: computedType(field.computed.resultType) };
155
155
  }
156
- return { required: field.required, rendered: scalarType(field.type, field.options) };
156
+ const scalar = scalarType(field.type, field.options);
157
+ // translated (§28.2): one language per read — `locale` on the read picks it
158
+ return { required: field.required, rendered: field.localized === true ? { ...scalar, note: `${scalar.note}, translated` } : scalar };
157
159
  }
158
160
  /** Unique type names: the singular PascalCase of the key; the raw
159
161
  PascalCase when two keys meet there; a numeric suffix as a last resort. */
@@ -8,4 +8,8 @@
8
8
  export const VANILLA_DIGESTS = {
9
9
  "0.1.0": "sha384-mbuMcQAueviAbkTzgvxc8oDtspuYH6lFLUgE+gocGexnI8ehgSgdjnNMNL7JPk4q",
10
10
  "0.1.1": "sha384-2vgQ4g9re/eiYxEa/H3WKpWI5C9L4wCB/62c8Kq+LOfZDzmqc2ozqMGC8mZoOUqk",
11
+ "0.1.2": "sha384-bhIJoFuEGouAZtHbYeNZeN0m6fqFVWpQw+YkSMJhfk/7m5bUwS/qVWZ3xczw/1du",
12
+ "0.2.0": "sha384-HJ5XG9AhAlbKe6jPRVaef3whiVup/5WhprMwqC6RwWhpq6Pb0OsRLzbLmLJ8WH0f",
13
+ "0.2.1": "sha384-6apTHYxXrozzwFCqNU+70Z9pa/FDnhArNomcKQNWbeKRX4scjFiOc9d7wWKCOvTU",
14
+ "0.2.2": "sha384-lfhwQoPUtrbLcD2HXSLr2o1z+aUAjotB62i3LFMGxzBlK5lfCQPQPLOoWL+1DBMc",
11
15
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mapled/cli",
3
- "version": "0.14.1",
3
+ "version": "0.14.3",
4
4
  "description": "Mapled CLI — sign in, link a project, generate TypeScript types for its content, create forms, preview drafts and check the site's integration.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://mapled.io",