@mapled/cli 0.8.0 → 0.9.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 CHANGED
@@ -175,7 +175,7 @@ Mapled doctor — Dolphin Landing
175
175
  ✓ Environment MAPLED_KEY and MAPLED_WEBHOOK_SECRET in .env.local
176
176
  ✓ Secrets in git No env files or Mapled secrets are tracked.
177
177
  ✓ @mapled/next 0.8.0 (current)
178
- ✓ mapled CLI 0.8.0 (current)
178
+ ✓ mapled CLI 0.9.0 (current)
179
179
  ✓ Revalidation route app/api/mapled/revalidate/route.ts
180
180
  ⚠ Preview route app/api/mapled/preview/route.ts is missing — mount createPreviewHandler from "@mapled/next/server" so Preview from Mapled works.
181
181
  ✓ Site reads content Last read 3m ago
@@ -188,7 +188,7 @@ Mapled doctor — Dolphin Landing
188
188
 
189
189
  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. 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.
190
190
 
191
- 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` (or `@mapled/client`), 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. doctor takes the framework from `mapled.json`, or works it out from `package.json`. 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.
191
+ 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. 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.
192
192
 
193
193
  Exit code 1 when something is marked ✗; `--json` prints the checks for scripts and agents.
194
194
 
package/dist/commands.js CHANGED
@@ -581,9 +581,10 @@ export async function doctor(ctx, flags) {
581
581
  const checks = [];
582
582
  checks.push(dr.checkLink(found ? { path: path.relative(ctx.cwd, found.path) || CONFIG_FILE } : null, status?.project.name ?? conn?.projectName ?? null));
583
583
  checks.push(dr.checkAuth({ conn: conn ?? null, status, error: authError }));
584
+ const framework = found?.config.framework ?? (await dr.detectFramework(dir));
584
585
  const typesPath = found?.config.types ?? DEFAULT_TYPES_PATH;
585
586
  const existing = await readFile(path.join(dir, typesPath), "utf8").catch(() => null);
586
- checks.push(dr.checkTypes(existing, schema ? generateTypes(schema, { projectName: status?.project.name, sdk: dr.sdkOf(found?.config.framework ?? (await dr.detectFramework(dir))) }) : null, typesPath));
587
+ checks.push(dr.checkTypes(existing, schema ? generateTypes(schema, { projectName: status?.project.name, sdk: dr.sdkOf(framework) }) : null, typesPath, framework));
587
588
  const pin = found ? await readPin(dir).catch(() => null) : null;
588
589
  const livePin = schema && found ? pinSchema(schema, found.config.project) : null;
589
590
  const pinChanges = pin && livePin ? diffSchema(pin.pin.collections, livePin.collections) : null;
@@ -601,13 +602,13 @@ export async function doctor(ctx, flags) {
601
602
  checks.push(dr.checkManifestFile(localManifest ? { manifest: localManifest.manifest, problems: localManifest.problems } : null, remoteManifest));
602
603
  checks.push(dr.checkIntegration({ pin: pin?.pin ?? null, manifest: localManifest?.manifest ?? null }, conn && status ? { integration: status.integration, manifest: remoteManifest } : null));
603
604
  checks.push(checkMapledMd(await readFile(path.join(dir, MD_FILE), "utf8").catch(() => null), guide));
604
- const framework = found?.config.framework ?? (await dr.detectFramework(dir));
605
605
  // a site rendered in the browser has no webhook, no routes and no secret to check
606
606
  const browser = dr.rendersInBrowser(framework);
607
607
  const sdk = dr.sdkOf(framework);
608
608
  checks.push(dr.checkEnv(await dr.envNames(dir), ctx.env, framework));
609
609
  checks.push(dr.checkSecrets(await dr.gitFacts(dir)));
610
- checks.push(dr.checkSdk(await dr.installedVersion(dir, sdk), status?.sdk[sdk], sdk));
610
+ // plain HTML installs nothing: the runtime comes with a script tag
611
+ checks.push(framework === "plain-html" ? dr.checkScriptTag(await dr.findScriptTag(dir), status?.sdk[sdk]) : dr.checkSdk(await dr.installedVersion(dir, sdk), status?.sdk[sdk], sdk));
611
612
  checks.push(dr.checkCli(ctx.version, status?.sdk["@mapled/cli"]));
612
613
  checks.push(dr.checkRoute("revalidate", await dr.findRoute(dir, "revalidate"), framework));
613
614
  checks.push(dr.checkRoute("preview", await dr.findRoute(dir, "preview"), framework));
package/dist/doctor.d.ts CHANGED
@@ -100,6 +100,18 @@ export declare function installedVersion(dir: string, pkg: string): Promise<Pack
100
100
  export declare const rendersInBrowser: (framework: string | null | undefined) => boolean;
101
101
  /** The first-party package a site of this framework reads through. */
102
102
  export declare const sdkOf: (framework: string | null | undefined) => string;
103
+ /** How a plain HTML site loads "@mapled/vanilla": a script tag in its
104
+ pages — from a CDN, where the version is in the address, or a copy of
105
+ the single file next to the pages, where it is in the file's first
106
+ line. */
107
+ export type ScriptTag = {
108
+ /** the page or the copy the version was read from */
109
+ file: string;
110
+ /** null — a CDN address without a version: it follows every release */
111
+ version: string | null;
112
+ };
113
+ export declare function findScriptTag(dir: string): Promise<ScriptTag | null>;
114
+ export declare function checkScriptTag(tag: ScriptTag | null, latest: string | undefined): Check;
103
115
  export declare function detectFramework(dir: string): Promise<string | undefined>;
104
116
  /** -1, 0 or 1 comparing dotted numeric versions; anything else compares by number. */
105
117
  export declare function compareVersions(a: string, b: string): number;
@@ -127,7 +139,7 @@ export declare function checkAuth(state: {
127
139
  status: IntegrationStatus | null;
128
140
  error: string | null;
129
141
  }): Check;
130
- export declare function checkTypes(existing: string | null, generated: Generated | null, typesPath: string): Check;
142
+ export declare function checkTypes(existing: string | null, generated: Generated | null, typesPath: string, framework?: string | null): Check;
131
143
  export declare function checkEnv(names: Map<string, string>, env: NodeJS.ProcessEnv, framework?: string | null): Check;
132
144
  export declare function checkSecrets(facts: GitFacts): Check;
133
145
  export declare function checkSdk(pkg: PackageVersion, latest: string | undefined, name?: string): Check;
package/dist/doctor.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { execFile } from "node:child_process";
2
- import { access, readFile } from "node:fs/promises";
2
+ 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";
@@ -108,7 +108,65 @@ export async function installedVersion(dir, pkg) {
108
108
  keys are the ones Mapled knows by (lib/siteStack.ts of the API). */
109
109
  export const rendersInBrowser = (framework) => framework === "react-spa" || framework === "plain-html";
110
110
  /** The first-party package a site of this framework reads through. */
111
- export const sdkOf = (framework) => !framework || framework === "nextjs" ? "@mapled/next" : framework === "react-spa" ? "@mapled/react" : "@mapled/client";
111
+ export const sdkOf = (framework) => !framework || framework === "nextjs" ? "@mapled/next" : framework === "react-spa" ? "@mapled/react" : framework === "plain-html" ? "@mapled/vanilla" : "@mapled/client";
112
+ const SKIPPED_DIRS = new Set(["node_modules", ".git", ".next", ".vercel", ".netlify", "vendor"]);
113
+ export async function findScriptTag(dir) {
114
+ // pages first, breadth first, and not forever: a site's pages sit near its root
115
+ const queue = [{ abs: dir, rel: "", depth: 0 }];
116
+ const copies = [];
117
+ let unpinned = null;
118
+ let seen = 0;
119
+ while (queue.length > 0 && seen < 2000) {
120
+ const at = queue.shift();
121
+ const entries = await readdir(at.abs, { withFileTypes: true }).catch(() => []);
122
+ for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
123
+ seen += 1;
124
+ const rel = at.rel ? `${at.rel}/${entry.name}` : entry.name;
125
+ if (entry.isDirectory()) {
126
+ if (at.depth < 3 && !SKIPPED_DIRS.has(entry.name) && !entry.name.startsWith("."))
127
+ queue.push({ abs: path.join(at.abs, entry.name), rel, depth: at.depth + 1 });
128
+ continue;
129
+ }
130
+ if (/^mapled(\.min)?\.js$/.test(entry.name))
131
+ copies.push(rel);
132
+ if (!/\.html?$/i.test(entry.name))
133
+ continue;
134
+ const text = await readFile(path.join(at.abs, entry.name), "utf8").catch(() => "");
135
+ const pinned = /@mapled\/vanilla@(\d+\.\d+\.\d+)/.exec(text);
136
+ if (pinned)
137
+ return { file: rel, version: pinned[1] };
138
+ if (unpinned === null && /@mapled\/vanilla(@latest)?\//.test(text))
139
+ unpinned = rel;
140
+ }
141
+ }
142
+ for (const rel of copies) {
143
+ const head = (await readFile(path.join(dir, rel), "utf8").catch(() => "")).slice(0, 200);
144
+ const banner = /^\/\*! @mapled\/vanilla (\d+\.\d+\.\d+) /.exec(head);
145
+ if (banner)
146
+ return { file: rel, version: banner[1] };
147
+ }
148
+ return unpinned === null ? null : { file: unpinned, version: null };
149
+ }
150
+ export function checkScriptTag(tag, latest) {
151
+ const key = "sdk";
152
+ const label = "@mapled/vanilla";
153
+ const at = latest ?? "x.y.z";
154
+ if (!tag) {
155
+ return {
156
+ key,
157
+ label,
158
+ status: "failed",
159
+ detail: `No page loads it yet. Put <script src="https://cdn.jsdelivr.net/npm/@mapled/vanilla@${at}/dist/mapled.js" data-mapled-key="…" defer></script> into the pages.`,
160
+ };
161
+ }
162
+ if (tag.version === null) {
163
+ return { key, label, status: "warning", detail: `${tag.file} loads it without a version — that address follows every release. Pin it: @mapled/vanilla@${at}.` };
164
+ }
165
+ if (latest && compareVersions(tag.version, latest) < 0) {
166
+ return { key, label, status: "warning", detail: `${tag.version} in ${tag.file}, ${latest} available — change the version in the script tag.` };
167
+ }
168
+ return { key, label, status: "passed", detail: latest ? `${tag.version} in ${tag.file} (current)` : `${tag.version} in ${tag.file}` };
169
+ }
112
170
  export async function detectFramework(dir) {
113
171
  const hasIndexHtml = () => access(path.join(dir, "index.html")).then(() => true, () => false);
114
172
  let deps;
@@ -234,8 +292,11 @@ export function checkAuth(state) {
234
292
  detail: `${state.status.connection.name} • ${state.status.project.name}`,
235
293
  };
236
294
  }
237
- export function checkTypes(existing, generated, typesPath) {
295
+ export function checkTypes(existing, generated, typesPath, framework) {
238
296
  const label = "Generated types";
297
+ if (framework === "plain-html" && existing === null) {
298
+ return { key: "types", label, status: "skipped", detail: "Not needed: plain HTML names its fields in attributes, not in TypeScript." };
299
+ }
239
300
  if (!generated)
240
301
  return { key: "types", label, status: "skipped", detail: "Sign in to compare with the schema." };
241
302
  if (existing === null) {
package/dist/types.js CHANGED
@@ -179,8 +179,8 @@ export function typeNames(collections) {
179
179
  export function generateTypes(schema, opts = {}) {
180
180
  // the package the site reads through: "@mapled/next" unless the framework says otherwise
181
181
  const sdk = opts.sdk ?? "@mapled/next";
182
- // a React app's assetUrl comes from the client its hooks are built on
183
- const assets = sdk === "@mapled/react" ? "@mapled/client" : sdk;
182
+ // a React app's assetUrl comes from the client its hooks are built on; the HTML runtime has none to call
183
+ const assets = sdk === "@mapled/react" || sdk === "@mapled/vanilla" ? "@mapled/client" : sdk;
184
184
  const hash = schemaHash(schema);
185
185
  const names = typeNames(schema.collections);
186
186
  const blocks = [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mapled/cli",
3
- "version": "0.8.0",
3
+ "version": "0.9.0",
4
4
  "description": "Mapled CLI — sign in, link a project, generate TypeScript types for its content, create forms and check the site's integration.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://mapled.io",