nuxtseo-shared 5.2.5 → 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.mjs CHANGED
@@ -2,6 +2,7 @@ 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';
@@ -19,9 +20,23 @@ const UNIFIED_CLIENT_ROUTE = "/__nuxt-seo-devtools";
19
20
  const hashSet = (arr) => [...JSON.stringify(arr)].reduce((h, c) => h * 31 + c.charCodeAt(0) >>> 0, 7).toString(36);
20
21
  function placeholderHtml() {
21
22
  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:18px}.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}</style></head>
23
- <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>
24
- <script>setInterval(async()=>{try{const r=await fetch('${UNIFIED_CLIENT_ROUTE}/__status');const j=await r.json();if(j.ready)location.reload()}catch{}},1000)<\/script></body></html>`;
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=>({'<':'&lt;','>':'&gt;','&':'&amp;'}[c])):'')+t
36
+ }catch{}
37
+ }
38
+ setInterval(poll,800);poll()
39
+ <\/script></body></html>`;
25
40
  }
26
41
  function deriveRoutes(layerDir, slug) {
27
42
  const routes = [`/${slug}`];
@@ -71,7 +86,30 @@ function resolveBaseLayer(installed) {
71
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.");
72
87
  return "nuxtseo-layer-devtools";
73
88
  }
74
- function generateAndBuild(cacheDir, installed, onReady) {
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) {
75
113
  const routes = ["/", ...installed.flatMap((m) => deriveRoutes(m.layerDir, m.slug))];
76
114
  const extendsList = [resolveBaseLayer(installed), ...installed.map((m) => m.layerDir)];
77
115
  mkdirSync(join(cacheDir, "pages"), { recursive: true });
@@ -92,12 +130,29 @@ export default defineNuxtConfig({
92
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>
93
131
  `);
94
132
  console.log(`[nuxt-seo] building devtools client for: ${installed.map((m) => m.slug).join(", ")}`);
95
- const child = spawn("npx", ["nuxi", "build"], { cwd: cacheDir, stdio: "inherit" });
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
+ });
96
148
  child.on("exit", (code) => {
97
149
  if (code === 0) {
98
150
  writeFileSync(join(cacheDir, ".installed-hash"), hashSet(installed.map((m) => m.slug).sort()));
99
- onReady();
151
+ hooks.onReady();
100
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();
101
156
  }
102
157
  });
103
158
  }
@@ -114,24 +169,52 @@ function setupLayerModule(config, layerDir, nuxt) {
114
169
  nuxt._seoDevtoolsInit = true;
115
170
  const cacheDir = join(nuxt.options.rootDir, "node_modules/.cache/nuxt-seo-devtools");
116
171
  const dist = join(cacheDir, "dist/devtools");
117
- 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
+ }
118
197
  nuxt.hook("modules:done", () => {
119
198
  const installed = nuxt._seoDevtoolsLayers;
120
199
  const key = hashSet(installed.map((m) => m.slug).sort());
121
200
  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
201
  });
127
202
  nuxt.hook("vite:serverCreated", (server) => {
128
203
  const serve = sirv(dist, { dev: true, single: "200.html" });
129
204
  server.middlewares.use(UNIFIED_CLIENT_ROUTE, (req, res, next) => {
130
205
  if ((req.url || "/").startsWith("/__status")) {
206
+ ensureBuilt();
131
207
  res.setHeader("content-type", "application/json");
132
- return res.end(JSON.stringify({ ready: state.ready }));
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
+ }));
133
215
  }
134
216
  if (!state.ready) {
217
+ ensureBuilt();
135
218
  res.setHeader("content-type", "text/html");
136
219
  return res.end(placeholderHtml());
137
220
  }
package/dist/module.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "compatibility": {
5
5
  "nuxt": ">=3.16.0"
6
6
  },
7
- "version": "5.2.5",
7
+ "version": "5.2.6",
8
8
  "builder": {
9
9
  "@nuxt/module-builder": "1.0.2",
10
10
  "unbuild": "3.6.1"
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "nuxtseo-shared",
3
3
  "type": "module",
4
- "version": "5.2.5",
4
+ "version": "5.2.6",
5
5
  "description": "Shared utilities for Nuxt SEO modules.",
6
6
  "author": {
7
7
  "name": "Harlan Wilton",