nuxtseo-shared 5.2.5 → 5.3.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/dist/devtools.mjs +197 -14
- package/dist/module.json +1 -1
- package/package.json +3 -2
package/dist/devtools.mjs
CHANGED
|
@@ -2,8 +2,10 @@ 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
|
|
5
|
+
import process from 'node:process';
|
|
6
|
+
import { onDevToolsInitialized, extendServerRpc, addCustomTab, startSubprocess } from '@nuxt/devtools-kit';
|
|
6
7
|
import { useNuxt } from '@nuxt/kit';
|
|
8
|
+
import { detectPackageManager } from 'nypm';
|
|
7
9
|
import sirv from 'sirv';
|
|
8
10
|
import { modules } from './const.mjs';
|
|
9
11
|
import { detectNuxtSeoModules } from './kit.mjs';
|
|
@@ -16,12 +18,48 @@ function npmForSlug(slug) {
|
|
|
16
18
|
return modules.find((m) => m.slug === slug)?.npm;
|
|
17
19
|
}
|
|
18
20
|
const UNIFIED_CLIENT_ROUTE = "/__nuxt-seo-devtools";
|
|
21
|
+
const TOOLCHAIN_PACKAGE = "nuxtseo-layer-devtools";
|
|
19
22
|
const hashSet = (arr) => [...JSON.stringify(arr)].reduce((h, c) => h * 31 + c.charCodeAt(0) >>> 0, 7).toString(36);
|
|
20
23
|
function placeholderHtml() {
|
|
21
24
|
return `<!doctype html><html><head><meta charset="utf-8"><title>Nuxt SEO DevTools</title>
|
|
22
|
-
<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:
|
|
23
|
-
<body><div class="wrap"><div class="spin"></div><h1>
|
|
24
|
-
<script>
|
|
25
|
+
<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;max-width:440px}.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)}.btn{font:inherit;font-size:13px;font-weight:600;padding:7px 16px;border-radius:8px;border:0;background:oklch(54% 0.225 292);color:#fff;cursor:pointer}.btn:hover{background:oklch(49% 0.225 292)}.btn:disabled{opacity:.6;cursor:default}.hide{display:none}.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>
|
|
26
|
+
<body><div class="wrap" id="wrap"><div class="spin" id="spin"></div><h1 id="title">Nuxt SEO DevTools</h1><div class="mods" id="mods"></div><p id="desc">Starting\u2026</p><button class="btn hide" id="install">Install</button><div class="step" id="step"></div></div>
|
|
27
|
+
<script>
|
|
28
|
+
const $=id=>document.getElementById(id)
|
|
29
|
+
const esc=s=>(s||'').replace(/[<>&]/g,c=>({'<':'<','>':'>','&':'&'}[c]))
|
|
30
|
+
let mods=false,clicked=false
|
|
31
|
+
$('install').addEventListener('click',async()=>{
|
|
32
|
+
clicked=true
|
|
33
|
+
$('install').disabled=true;$('install').classList.add('hide')
|
|
34
|
+
$('spin').classList.remove('hide')
|
|
35
|
+
$('title').textContent='Installing\u2026'
|
|
36
|
+
$('desc').textContent='Adding the dev-only DevTools UI to your project.'
|
|
37
|
+
try{await fetch('${UNIFIED_CLIENT_ROUTE}/__install')}catch{}
|
|
38
|
+
})
|
|
39
|
+
async function poll(){
|
|
40
|
+
try{
|
|
41
|
+
const j=await (await fetch('${UNIFIED_CLIENT_ROUTE}/__status')).json()
|
|
42
|
+
if(j.ready){location.reload();return}
|
|
43
|
+
if(!mods&&Array.isArray(j.modules)&&j.modules.length){mods=true;$('mods').innerHTML=j.modules.map(m=>'<span class="chip">'+esc(m)+'</span>').join('')}
|
|
44
|
+
if(j.failed){$('wrap').classList.add('err');$('spin').classList.remove('hide');$('install').classList.add('hide');$('title').textContent='DevTools unavailable';$('desc').textContent='';$('step').textContent=j.step||'';return}
|
|
45
|
+
if(j.needsInstall&&!clicked){
|
|
46
|
+
const pkg=j.packageName||'nuxtseo-devtools'
|
|
47
|
+
$('spin').classList.add('hide')
|
|
48
|
+
$('install').classList.remove('hide');$('install').disabled=false;$('install').textContent='Install '+pkg
|
|
49
|
+
$('title').textContent='Nuxt SEO DevTools'
|
|
50
|
+
$('desc').textContent='The panel needs '+pkg+' (dev only). Installing it adds the package to your devDependencies; nothing is changed until you choose to install.'
|
|
51
|
+
$('step').textContent=''
|
|
52
|
+
return
|
|
53
|
+
}
|
|
54
|
+
$('spin').classList.remove('hide');$('install').classList.add('hide')
|
|
55
|
+
$('title').textContent=j.installing?'Installing\u2026':'Building Nuxt SEO DevTools\u2026'
|
|
56
|
+
$('desc').textContent=j.installing?'Adding the dev-only DevTools UI to your project.':'Assembling panels for your installed modules. This runs once.'
|
|
57
|
+
const t=j.elapsed?' \xB7 <span class="t">'+Math.round(j.elapsed/1000)+'s</span>':''
|
|
58
|
+
$('step').innerHTML=esc(j.step)+t
|
|
59
|
+
}catch{}
|
|
60
|
+
}
|
|
61
|
+
setInterval(poll,800);poll()
|
|
62
|
+
<\/script></body></html>`;
|
|
25
63
|
}
|
|
26
64
|
function deriveRoutes(layerDir, slug) {
|
|
27
65
|
const routes = [`/${slug}`];
|
|
@@ -71,7 +109,68 @@ function resolveBaseLayer(installed) {
|
|
|
71
109
|
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.");
|
|
72
110
|
return "nuxtseo-layer-devtools";
|
|
73
111
|
}
|
|
74
|
-
function
|
|
112
|
+
function resolveNuxtCli(rootDir) {
|
|
113
|
+
const bases = [process.argv[1], join(rootDir, "index.js")].filter(Boolean);
|
|
114
|
+
for (const base of bases) {
|
|
115
|
+
let req;
|
|
116
|
+
try {
|
|
117
|
+
req = createRequire(base);
|
|
118
|
+
} catch {
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
for (const spec of ["nuxi", "nuxt", "@nuxt/cli"]) {
|
|
122
|
+
try {
|
|
123
|
+
const pkgPath = req.resolve(`${spec}/package.json`);
|
|
124
|
+
const bin = JSON.parse(readFileSync(pkgPath, "utf8")).bin;
|
|
125
|
+
const rel = typeof bin === "string" ? bin : bin?.nuxi ?? bin?.nuxt;
|
|
126
|
+
if (rel)
|
|
127
|
+
return join(dirname(pkgPath), rel);
|
|
128
|
+
} catch {
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
const stripAnsi = (s) => s.replace(new RegExp(`${String.fromCharCode(27)}\\[[0-9;]*m`, "g"), "");
|
|
135
|
+
function toolchainInstalled(rootDir) {
|
|
136
|
+
try {
|
|
137
|
+
createRequire(join(rootDir, "index.js")).resolve(TOOLCHAIN_PACKAGE);
|
|
138
|
+
return true;
|
|
139
|
+
} catch {
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
async function devAddArgs(rootDir) {
|
|
144
|
+
const pm = await detectPackageManager(rootDir).catch(() => null);
|
|
145
|
+
const name = pm?.name ?? "npm";
|
|
146
|
+
if (name === "npm")
|
|
147
|
+
return { command: "npm", args: ["install", "--save-dev", TOOLCHAIN_PACKAGE] };
|
|
148
|
+
if (name === "yarn")
|
|
149
|
+
return { command: "yarn", args: ["add", "--dev", TOOLCHAIN_PACKAGE] };
|
|
150
|
+
return { command: name, args: ["add", "-D", TOOLCHAIN_PACKAGE] };
|
|
151
|
+
}
|
|
152
|
+
async function installToolchain(rootDir, nuxt, onProgress) {
|
|
153
|
+
const { command, args } = await devAddArgs(rootDir);
|
|
154
|
+
onProgress(`Installing ${TOOLCHAIN_PACKAGE} with ${command}\u2026`);
|
|
155
|
+
return await new Promise((resolve) => {
|
|
156
|
+
const { getProcess } = startSubprocess(
|
|
157
|
+
{ command, args, cwd: rootDir },
|
|
158
|
+
{ id: "nuxt-seo:install-devtools", name: `Install ${TOOLCHAIN_PACKAGE}`, icon: "carbon:download" },
|
|
159
|
+
nuxt
|
|
160
|
+
);
|
|
161
|
+
const proc = getProcess();
|
|
162
|
+
if (!proc) {
|
|
163
|
+
resolve(false);
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
proc.on("exit", (code) => resolve(code === 0 && toolchainInstalled(rootDir)));
|
|
167
|
+
proc.on("error", (err) => {
|
|
168
|
+
console.error(`[nuxt-seo] could not run "${command} ${args.join(" ")}": ${err.message}`);
|
|
169
|
+
resolve(false);
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
function generateAndBuild(cacheDir, rootDir, installed, hooks) {
|
|
75
174
|
const routes = ["/", ...installed.flatMap((m) => deriveRoutes(m.layerDir, m.slug))];
|
|
76
175
|
const extendsList = [resolveBaseLayer(installed), ...installed.map((m) => m.layerDir)];
|
|
77
176
|
mkdirSync(join(cacheDir, "pages"), { recursive: true });
|
|
@@ -92,12 +191,29 @@ export default defineNuxtConfig({
|
|
|
92
191
|
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>
|
|
93
192
|
`);
|
|
94
193
|
console.log(`[nuxt-seo] building devtools client for: ${installed.map((m) => m.slug).join(", ")}`);
|
|
95
|
-
const
|
|
194
|
+
const cliBin = resolveNuxtCli(rootDir);
|
|
195
|
+
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 });
|
|
196
|
+
const statusLine = /^(?:[ℹ✔✓✨⚠✖✗●➜√]|\[\w)/;
|
|
197
|
+
const forward = (chunk, sink) => {
|
|
198
|
+
sink.write(chunk);
|
|
199
|
+
const step = stripAnsi(chunk.toString()).split("\n").map((l) => l.trim()).filter((l) => statusLine.test(l)).pop();
|
|
200
|
+
if (step)
|
|
201
|
+
hooks.onProgress(step);
|
|
202
|
+
};
|
|
203
|
+
child.stdout?.on("data", (c) => forward(c, process.stdout));
|
|
204
|
+
child.stderr?.on("data", (c) => forward(c, process.stderr));
|
|
205
|
+
child.on("error", (err) => {
|
|
206
|
+
console.error(`[nuxt-seo] could not build devtools client, the panel will stay unavailable: ${err.message}`);
|
|
207
|
+
hooks.onError();
|
|
208
|
+
});
|
|
96
209
|
child.on("exit", (code) => {
|
|
97
210
|
if (code === 0) {
|
|
98
211
|
writeFileSync(join(cacheDir, ".installed-hash"), hashSet(installed.map((m) => m.slug).sort()));
|
|
99
|
-
onReady();
|
|
212
|
+
hooks.onReady();
|
|
100
213
|
console.log("[nuxt-seo] devtools client ready");
|
|
214
|
+
} else {
|
|
215
|
+
console.error(`[nuxt-seo] devtools client build exited with code ${code}, the panel will stay unavailable`);
|
|
216
|
+
hooks.onError();
|
|
101
217
|
}
|
|
102
218
|
});
|
|
103
219
|
}
|
|
@@ -114,22 +230,89 @@ function setupLayerModule(config, layerDir, nuxt) {
|
|
|
114
230
|
nuxt._seoDevtoolsInit = true;
|
|
115
231
|
const cacheDir = join(nuxt.options.rootDir, "node_modules/.cache/nuxt-seo-devtools");
|
|
116
232
|
const dist = join(cacheDir, "dist/devtools");
|
|
117
|
-
const state = { ready: false };
|
|
233
|
+
const state = { ready: false, building: false, installing: false, needsInstall: false, failed: false, startedAt: 0, step: "", modules: [] };
|
|
234
|
+
const rootDir = nuxt.options.rootDir;
|
|
235
|
+
function ensureBuilt() {
|
|
236
|
+
if (state.ready || state.building)
|
|
237
|
+
return;
|
|
238
|
+
state.building = true;
|
|
239
|
+
state.failed = false;
|
|
240
|
+
state.startedAt = Date.now();
|
|
241
|
+
state.step = "Starting build\u2026";
|
|
242
|
+
const installed = nuxt._seoDevtoolsLayers;
|
|
243
|
+
state.modules = installed.map((m) => m.title);
|
|
244
|
+
generateAndBuild(cacheDir, rootDir, installed, {
|
|
245
|
+
onProgress: (step) => {
|
|
246
|
+
state.step = step;
|
|
247
|
+
},
|
|
248
|
+
onReady: () => {
|
|
249
|
+
state.ready = true;
|
|
250
|
+
state.building = false;
|
|
251
|
+
},
|
|
252
|
+
onError: (message) => {
|
|
253
|
+
state.building = false;
|
|
254
|
+
state.failed = true;
|
|
255
|
+
state.step = message ?? "Build failed, see the dev server logs";
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
function startInstall() {
|
|
260
|
+
if (state.installing || state.building || state.ready)
|
|
261
|
+
return;
|
|
262
|
+
state.installing = true;
|
|
263
|
+
state.needsInstall = false;
|
|
264
|
+
state.failed = false;
|
|
265
|
+
state.startedAt = Date.now();
|
|
266
|
+
state.step = `Installing ${TOOLCHAIN_PACKAGE}\u2026`;
|
|
267
|
+
installToolchain(rootDir, nuxt, (step) => {
|
|
268
|
+
state.step = step;
|
|
269
|
+
}).then((ok) => {
|
|
270
|
+
state.installing = false;
|
|
271
|
+
if (ok) {
|
|
272
|
+
ensureBuilt();
|
|
273
|
+
} else {
|
|
274
|
+
state.failed = true;
|
|
275
|
+
state.step = `Could not install ${TOOLCHAIN_PACKAGE} \u2014 see the dev server logs`;
|
|
276
|
+
}
|
|
277
|
+
}).catch((err) => {
|
|
278
|
+
state.installing = false;
|
|
279
|
+
state.failed = true;
|
|
280
|
+
state.step = `Could not install ${TOOLCHAIN_PACKAGE} \u2014 see the dev server logs`;
|
|
281
|
+
console.error(`[nuxt-seo] devtools toolchain install failed: ${err.message}`);
|
|
282
|
+
});
|
|
283
|
+
}
|
|
118
284
|
nuxt.hook("modules:done", () => {
|
|
119
285
|
const installed = nuxt._seoDevtoolsLayers;
|
|
120
286
|
const key = hashSet(installed.map((m) => m.slug).sort());
|
|
121
287
|
state.ready = existsSync(join(cacheDir, ".installed-hash")) && readFileSync(join(cacheDir, ".installed-hash"), "utf8") === key && existsSync(dist);
|
|
122
|
-
if (!state.ready)
|
|
123
|
-
generateAndBuild(cacheDir, installed, () => {
|
|
124
|
-
state.ready = true;
|
|
125
|
-
});
|
|
126
288
|
});
|
|
127
289
|
nuxt.hook("vite:serverCreated", (server) => {
|
|
128
290
|
const serve = sirv(dist, { dev: true, single: "200.html" });
|
|
129
291
|
server.middlewares.use(UNIFIED_CLIENT_ROUTE, (req, res, next) => {
|
|
130
|
-
|
|
292
|
+
const url = req.url || "/";
|
|
293
|
+
if (url.startsWith("/__install")) {
|
|
294
|
+
startInstall();
|
|
295
|
+
res.setHeader("content-type", "application/json");
|
|
296
|
+
return res.end(JSON.stringify({ installing: state.installing }));
|
|
297
|
+
}
|
|
298
|
+
if (url.startsWith("/__status")) {
|
|
299
|
+
if (!state.ready && !state.building && !state.installing) {
|
|
300
|
+
if (toolchainInstalled(rootDir))
|
|
301
|
+
ensureBuilt();
|
|
302
|
+
else
|
|
303
|
+
state.needsInstall = true;
|
|
304
|
+
}
|
|
131
305
|
res.setHeader("content-type", "application/json");
|
|
132
|
-
return res.end(JSON.stringify({
|
|
306
|
+
return res.end(JSON.stringify({
|
|
307
|
+
ready: state.ready,
|
|
308
|
+
failed: state.failed,
|
|
309
|
+
needsInstall: state.needsInstall,
|
|
310
|
+
installing: state.installing,
|
|
311
|
+
packageName: TOOLCHAIN_PACKAGE,
|
|
312
|
+
step: state.step,
|
|
313
|
+
modules: state.modules,
|
|
314
|
+
elapsed: state.startedAt ? Date.now() - state.startedAt : 0
|
|
315
|
+
}));
|
|
133
316
|
}
|
|
134
317
|
if (!state.ready) {
|
|
135
318
|
res.setHeader("content-type", "text/html");
|
package/dist/module.json
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nuxtseo-shared",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "5.
|
|
4
|
+
"version": "5.3.0",
|
|
5
5
|
"description": "Shared utilities for Nuxt SEO modules.",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Harlan Wilton",
|
|
@@ -81,6 +81,7 @@
|
|
|
81
81
|
"birpc": "^4.0.0",
|
|
82
82
|
"consola": "^3.4.2",
|
|
83
83
|
"defu": "^6.1.7",
|
|
84
|
+
"nypm": "^0.6.6",
|
|
84
85
|
"ofetch": "^1.5.1",
|
|
85
86
|
"pathe": "^2.0.3",
|
|
86
87
|
"pkg-types": "^2.3.1",
|
|
@@ -96,7 +97,7 @@
|
|
|
96
97
|
"@nuxtjs/i18n": "^10.4.0",
|
|
97
98
|
"nitropack": "^2.13.4",
|
|
98
99
|
"nuxt": "^4.4.8",
|
|
99
|
-
"nuxt-site-config": "^4.0
|
|
100
|
+
"nuxt-site-config": "^4.1.0",
|
|
100
101
|
"typescript": "^6.0.3",
|
|
101
102
|
"vitest": "^4.1.8",
|
|
102
103
|
"vue": "^3.5.35",
|