nuxtseo-shared 5.2.4 → 5.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/devtools.d.mts +2 -0
- package/dist/devtools.d.ts +2 -0
- package/dist/devtools.mjs +129 -18
- package/dist/module.json +1 -1
- package/package.json +1 -1
package/dist/devtools.d.mts
CHANGED
package/dist/devtools.d.ts
CHANGED
package/dist/devtools.mjs
CHANGED
|
@@ -2,17 +2,41 @@ import { spawn } from 'node:child_process';
|
|
|
2
2
|
import { existsSync, readFileSync, readdirSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
3
3
|
import { createRequire } from 'node:module';
|
|
4
4
|
import { join, dirname } from 'node:path';
|
|
5
|
+
import process from 'node:process';
|
|
5
6
|
import { onDevToolsInitialized, extendServerRpc, addCustomTab } from '@nuxt/devtools-kit';
|
|
6
7
|
import { useNuxt } from '@nuxt/kit';
|
|
7
8
|
import sirv from 'sirv';
|
|
9
|
+
import { modules } from './const.mjs';
|
|
10
|
+
import { detectNuxtSeoModules } from './kit.mjs';
|
|
11
|
+
import 'node:url';
|
|
12
|
+
import 'pathe';
|
|
13
|
+
import 'pkg-types';
|
|
14
|
+
import 'std-env';
|
|
8
15
|
|
|
16
|
+
function npmForSlug(slug) {
|
|
17
|
+
return modules.find((m) => m.slug === slug)?.npm;
|
|
18
|
+
}
|
|
9
19
|
const UNIFIED_CLIENT_ROUTE = "/__nuxt-seo-devtools";
|
|
10
20
|
const hashSet = (arr) => [...JSON.stringify(arr)].reduce((h, c) => h * 31 + c.charCodeAt(0) >>> 0, 7).toString(36);
|
|
11
21
|
function placeholderHtml() {
|
|
12
22
|
return `<!doctype html><html><head><meta charset="utf-8"><title>Nuxt SEO DevTools</title>
|
|
13
|
-
<style>html,body{margin:0;height:100%;font-family:'Hubot Sans',system-ui,sans-serif;background:oklch(98.4% 0.005 292);color:oklch(16% 0.036 292)}@media(prefers-color-scheme:dark){html,body{background:oklch(11% 0.029 292);color:oklch(96.8% 0.009 292)}}.wrap{height:100%;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:
|
|
14
|
-
<body><div class="wrap"><div class="spin"></div><h1>Building Nuxt SEO DevTools\u2026</h1><p>Assembling panels for your installed modules. This runs once.</p></div>
|
|
15
|
-
<script>
|
|
23
|
+
<style>html,body{margin:0;height:100%;font-family:'Hubot Sans',system-ui,sans-serif;background:oklch(98.4% 0.005 292);color:oklch(16% 0.036 292)}@media(prefers-color-scheme:dark){html,body{background:oklch(11% 0.029 292);color:oklch(96.8% 0.009 292)}}.wrap{height:100%;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:14px;padding:24px;text-align:center}.spin{width:34px;height:34px;border-radius:50%;border:3px solid color-mix(in oklab,oklch(54% 0.225 292) 25%,transparent);border-top-color:oklch(54% 0.225 292);animation:s .8s linear infinite}@keyframes s{to{transform:rotate(360deg)}}h1{font-size:15px;font-weight:600;margin:0}p{font-size:13px;opacity:.6;margin:0}.mods{display:flex;flex-wrap:wrap;gap:6px;justify-content:center;max-width:420px}.chip{font-size:11px;padding:2px 9px;border-radius:999px;background:color-mix(in oklab,oklch(54% 0.225 292) 14%,transparent);color:oklch(54% 0.225 292)}.step{font-size:12px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;opacity:.55;max-width:520px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;min-height:16px}.t{font-variant-numeric:tabular-nums;opacity:.85}.err .spin{border-top-color:oklch(60% 0.2 25);border-color:color-mix(in oklab,oklch(60% 0.2 25) 25%,transparent);animation:none}</style></head>
|
|
24
|
+
<body><div class="wrap" id="wrap"><div class="spin"></div><h1 id="title">Building Nuxt SEO DevTools\u2026</h1><div class="mods" id="mods"></div><p>Assembling panels for your installed modules. This runs once.</p><div class="step" id="step"></div></div>
|
|
25
|
+
<script>
|
|
26
|
+
const $=id=>document.getElementById(id)
|
|
27
|
+
let mods=false
|
|
28
|
+
async function poll(){
|
|
29
|
+
try{
|
|
30
|
+
const j=await (await fetch('${UNIFIED_CLIENT_ROUTE}/__status')).json()
|
|
31
|
+
if(j.ready){location.reload();return}
|
|
32
|
+
if(!mods&&Array.isArray(j.modules)&&j.modules.length){mods=true;$('mods').innerHTML=j.modules.map(m=>'<span class="chip">'+m+'</span>').join('')}
|
|
33
|
+
if(j.failed){$('wrap').classList.add('err');$('title').textContent='DevTools build failed';$('step').textContent=j.step||'';return}
|
|
34
|
+
const t=j.elapsed?' \xB7 <span class="t">'+Math.round(j.elapsed/1000)+'s</span>':''
|
|
35
|
+
$('step').innerHTML=(j.step?j.step.replace(/[<>&]/g,c=>({'<':'<','>':'>','&':'&'}[c])):'')+t
|
|
36
|
+
}catch{}
|
|
37
|
+
}
|
|
38
|
+
setInterval(poll,800);poll()
|
|
39
|
+
<\/script></body></html>`;
|
|
16
40
|
}
|
|
17
41
|
function deriveRoutes(layerDir, slug) {
|
|
18
42
|
const routes = [`/${slug}`];
|
|
@@ -31,7 +55,24 @@ function registerSharedRpcOnce(nuxt) {
|
|
|
31
55
|
nuxt._seoDevtoolsRpcRegistered = true;
|
|
32
56
|
onDevToolsInitialized(() => {
|
|
33
57
|
extendServerRpc("nuxt-seo-modules", {
|
|
34
|
-
|
|
58
|
+
// Registered modules (those that shipped a devtools panel) carry the iframe route.
|
|
59
|
+
// detectNuxtSeoModules adds every *installed* SEO module from nuxt's module list —
|
|
60
|
+
// independent of whether it self-registered a panel or which shared version it ships —
|
|
61
|
+
// so the picker reflects the full install (e.g. site-config, which has no panel).
|
|
62
|
+
getInstalledSeoModules: () => {
|
|
63
|
+
const byNpm = /* @__PURE__ */ new Map();
|
|
64
|
+
for (const m of nuxt._seoDevtoolsModules || []) {
|
|
65
|
+
if (m.npm)
|
|
66
|
+
byNpm.set(m.npm, m);
|
|
67
|
+
}
|
|
68
|
+
for (const det of detectNuxtSeoModules(nuxt)) {
|
|
69
|
+
if (!byNpm.has(det.name)) {
|
|
70
|
+
const meta = modules.find((s) => s.npm === det.name);
|
|
71
|
+
byNpm.set(det.name, { name: meta?.slug ?? det.name, npm: det.name, title: meta?.label ?? det.name, icon: meta?.icon ?? "", route: "" });
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return [...byNpm.values()];
|
|
75
|
+
}
|
|
35
76
|
}, nuxt);
|
|
36
77
|
}, nuxt);
|
|
37
78
|
}
|
|
@@ -45,7 +86,30 @@ function resolveBaseLayer(installed) {
|
|
|
45
86
|
console.warn("[nuxt-seo] could not resolve nuxtseo-layer-devtools from any installed SEO module \u2014 add it to the module's dependencies. Falling back to a bare specifier, which only works when your app installs it directly.");
|
|
46
87
|
return "nuxtseo-layer-devtools";
|
|
47
88
|
}
|
|
48
|
-
function
|
|
89
|
+
function resolveNuxtCli(rootDir) {
|
|
90
|
+
const bases = [process.argv[1], join(rootDir, "index.js")].filter(Boolean);
|
|
91
|
+
for (const base of bases) {
|
|
92
|
+
let req;
|
|
93
|
+
try {
|
|
94
|
+
req = createRequire(base);
|
|
95
|
+
} catch {
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
for (const spec of ["nuxi", "nuxt", "@nuxt/cli"]) {
|
|
99
|
+
try {
|
|
100
|
+
const pkgPath = req.resolve(`${spec}/package.json`);
|
|
101
|
+
const bin = JSON.parse(readFileSync(pkgPath, "utf8")).bin;
|
|
102
|
+
const rel = typeof bin === "string" ? bin : bin?.nuxi ?? bin?.nuxt;
|
|
103
|
+
if (rel)
|
|
104
|
+
return join(dirname(pkgPath), rel);
|
|
105
|
+
} catch {
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
const stripAnsi = (s) => s.replace(new RegExp(`${String.fromCharCode(27)}\\[[0-9;]*m`, "g"), "");
|
|
112
|
+
function generateAndBuild(cacheDir, rootDir, installed, hooks) {
|
|
49
113
|
const routes = ["/", ...installed.flatMap((m) => deriveRoutes(m.layerDir, m.slug))];
|
|
50
114
|
const extendsList = [resolveBaseLayer(installed), ...installed.map((m) => m.layerDir)];
|
|
51
115
|
mkdirSync(join(cacheDir, "pages"), { recursive: true });
|
|
@@ -66,12 +130,29 @@ export default defineNuxtConfig({
|
|
|
66
130
|
writeFileSync(join(cacheDir, "pages/index.vue"), `<template><div class="p-4">${installed.map((m) => `<NuxtLink to="/${m.slug}" class="block underline">${m.title}</NuxtLink>`).join("")}</div></template>
|
|
67
131
|
`);
|
|
68
132
|
console.log(`[nuxt-seo] building devtools client for: ${installed.map((m) => m.slug).join(", ")}`);
|
|
69
|
-
const
|
|
133
|
+
const cliBin = resolveNuxtCli(rootDir);
|
|
134
|
+
const child = cliBin ? spawn(process.execPath, [cliBin, "build"], { cwd: cacheDir, stdio: ["inherit", "pipe", "pipe"] }) : spawn("npx", ["nuxi", "build"], { cwd: cacheDir, stdio: ["inherit", "pipe", "pipe"], shell: true });
|
|
135
|
+
const statusLine = /^(?:[ℹ✔✓✨⚠✖✗●➜√]|\[\w)/;
|
|
136
|
+
const forward = (chunk, sink) => {
|
|
137
|
+
sink.write(chunk);
|
|
138
|
+
const step = stripAnsi(chunk.toString()).split("\n").map((l) => l.trim()).filter((l) => statusLine.test(l)).pop();
|
|
139
|
+
if (step)
|
|
140
|
+
hooks.onProgress(step);
|
|
141
|
+
};
|
|
142
|
+
child.stdout?.on("data", (c) => forward(c, process.stdout));
|
|
143
|
+
child.stderr?.on("data", (c) => forward(c, process.stderr));
|
|
144
|
+
child.on("error", (err) => {
|
|
145
|
+
console.error(`[nuxt-seo] could not build devtools client, the panel will stay unavailable: ${err.message}`);
|
|
146
|
+
hooks.onError();
|
|
147
|
+
});
|
|
70
148
|
child.on("exit", (code) => {
|
|
71
149
|
if (code === 0) {
|
|
72
150
|
writeFileSync(join(cacheDir, ".installed-hash"), hashSet(installed.map((m) => m.slug).sort()));
|
|
73
|
-
onReady();
|
|
151
|
+
hooks.onReady();
|
|
74
152
|
console.log("[nuxt-seo] devtools client ready");
|
|
153
|
+
} else {
|
|
154
|
+
console.error(`[nuxt-seo] devtools client build exited with code ${code}, the panel will stay unavailable`);
|
|
155
|
+
hooks.onError();
|
|
75
156
|
}
|
|
76
157
|
});
|
|
77
158
|
}
|
|
@@ -79,7 +160,7 @@ function setupLayerModule(config, layerDir, nuxt) {
|
|
|
79
160
|
const slug = config.slug ?? config.name.replace(/^nuxt-/, "");
|
|
80
161
|
const clientRoute = `${UNIFIED_CLIENT_ROUTE}/${slug}`;
|
|
81
162
|
const modules = nuxt._seoDevtoolsModules ??= [];
|
|
82
|
-
modules.push({ name: config.name, title: config.title, icon: config.icon, route: clientRoute });
|
|
163
|
+
modules.push({ name: config.name, npm: npmForSlug(slug), title: config.title, icon: config.icon, route: clientRoute });
|
|
83
164
|
const layers = nuxt._seoDevtoolsLayers ??= [];
|
|
84
165
|
layers.push({ slug, name: config.name, title: config.title, icon: config.icon, layerDir });
|
|
85
166
|
addCustomTab({ name: `nuxt-seo-${slug}`, title: config.title, icon: config.icon, view: { type: "iframe", src: clientRoute } });
|
|
@@ -88,24 +169,52 @@ function setupLayerModule(config, layerDir, nuxt) {
|
|
|
88
169
|
nuxt._seoDevtoolsInit = true;
|
|
89
170
|
const cacheDir = join(nuxt.options.rootDir, "node_modules/.cache/nuxt-seo-devtools");
|
|
90
171
|
const dist = join(cacheDir, "dist/devtools");
|
|
91
|
-
const state = { ready: false };
|
|
172
|
+
const state = { ready: false, building: false, failed: false, startedAt: 0, step: "", modules: [] };
|
|
173
|
+
function ensureBuilt() {
|
|
174
|
+
if (state.ready || state.building)
|
|
175
|
+
return;
|
|
176
|
+
state.building = true;
|
|
177
|
+
state.failed = false;
|
|
178
|
+
state.startedAt = Date.now();
|
|
179
|
+
state.step = "Starting build\u2026";
|
|
180
|
+
const installed = nuxt._seoDevtoolsLayers;
|
|
181
|
+
state.modules = installed.map((m) => m.title);
|
|
182
|
+
generateAndBuild(cacheDir, nuxt.options.rootDir, installed, {
|
|
183
|
+
onProgress: (step) => {
|
|
184
|
+
state.step = step;
|
|
185
|
+
},
|
|
186
|
+
onReady: () => {
|
|
187
|
+
state.ready = true;
|
|
188
|
+
state.building = false;
|
|
189
|
+
},
|
|
190
|
+
onError: () => {
|
|
191
|
+
state.building = false;
|
|
192
|
+
state.failed = true;
|
|
193
|
+
state.step = "Build failed, see the dev server logs";
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
}
|
|
92
197
|
nuxt.hook("modules:done", () => {
|
|
93
198
|
const installed = nuxt._seoDevtoolsLayers;
|
|
94
199
|
const key = hashSet(installed.map((m) => m.slug).sort());
|
|
95
200
|
state.ready = existsSync(join(cacheDir, ".installed-hash")) && readFileSync(join(cacheDir, ".installed-hash"), "utf8") === key && existsSync(dist);
|
|
96
|
-
if (!state.ready)
|
|
97
|
-
generateAndBuild(cacheDir, installed, () => {
|
|
98
|
-
state.ready = true;
|
|
99
|
-
});
|
|
100
201
|
});
|
|
101
202
|
nuxt.hook("vite:serverCreated", (server) => {
|
|
102
203
|
const serve = sirv(dist, { dev: true, single: "200.html" });
|
|
103
204
|
server.middlewares.use(UNIFIED_CLIENT_ROUTE, (req, res, next) => {
|
|
104
205
|
if ((req.url || "/").startsWith("/__status")) {
|
|
206
|
+
ensureBuilt();
|
|
105
207
|
res.setHeader("content-type", "application/json");
|
|
106
|
-
return res.end(JSON.stringify({
|
|
208
|
+
return res.end(JSON.stringify({
|
|
209
|
+
ready: state.ready,
|
|
210
|
+
failed: state.failed,
|
|
211
|
+
step: state.step,
|
|
212
|
+
modules: state.modules,
|
|
213
|
+
elapsed: state.startedAt ? Date.now() - state.startedAt : 0
|
|
214
|
+
}));
|
|
107
215
|
}
|
|
108
216
|
if (!state.ready) {
|
|
217
|
+
ensureBuilt();
|
|
109
218
|
res.setHeader("content-type", "text/html");
|
|
110
219
|
return res.end(placeholderHtml());
|
|
111
220
|
}
|
|
@@ -115,9 +224,10 @@ function setupLayerModule(config, layerDir, nuxt) {
|
|
|
115
224
|
}
|
|
116
225
|
function setupLegacyModule(config, clientPath, nuxt) {
|
|
117
226
|
const { name, title, icon, devPort = 3030 } = config;
|
|
118
|
-
const
|
|
227
|
+
const slug = config.slug ?? name.replace(/^nuxt-/, "");
|
|
228
|
+
const route = config.route ?? `/__${slug}`;
|
|
119
229
|
const modules = nuxt._seoDevtoolsModules ??= [];
|
|
120
|
-
modules.push({ name, title, icon, route });
|
|
230
|
+
modules.push({ name, npm: npmForSlug(slug), title, icon, route });
|
|
121
231
|
const isProductionBuild = existsSync(clientPath) && readdirSync(clientPath).length > 0;
|
|
122
232
|
if (isProductionBuild) {
|
|
123
233
|
nuxt.hook("vite:serverCreated", (server) => {
|
|
@@ -155,8 +265,9 @@ function setupLegacyModule(config, clientPath, nuxt) {
|
|
|
155
265
|
function setupDevToolsUI(config, resolve, nuxt = useNuxt()) {
|
|
156
266
|
if (!nuxt.options.dev)
|
|
157
267
|
return;
|
|
158
|
-
const
|
|
159
|
-
const
|
|
268
|
+
const layerFallback = resolve("./devtools");
|
|
269
|
+
const layerCandidates = [layerFallback, resolve("../devtools")];
|
|
270
|
+
const layerDir = layerCandidates.find((dir) => existsSync(join(dir, "nuxt.config.ts"))) ?? layerFallback;
|
|
160
271
|
const isLayer = existsSync(join(layerDir, "nuxt.config.ts")) && !existsSync(join(layerDir, "index.html"));
|
|
161
272
|
registerSharedRpcOnce(nuxt);
|
|
162
273
|
if (isLayer)
|
package/dist/module.json
CHANGED