@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 +2 -2
- package/dist/commands.js +4 -3
- package/dist/doctor.d.ts +13 -1
- package/dist/doctor.js +64 -3
- package/dist/types.js +2 -2
- package/package.json +1 -1
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.
|
|
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
|
|
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(
|
|
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
|
-
|
|
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.
|
|
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",
|