UNPKG

@nuxt/devtools

Version:

<a href="https://devtools.nuxt.com"><img width="1200" alt="Nuxt DevTools" src="https://github-production-user-asset-6210df.s3.amazonaws.com/904724/261577617-a10567bd-ad33-48cc-9bda-9e37dbe1929f.png"></a> <br>

1,638 lines (1,613 loc) 61.6 kB
import fs$1, { existsSync } from 'node:fs'; import fs from 'node:fs/promises'; import os, { homedir } from 'node:os'; import { logger, useNuxt, addPlugin, addTemplate, addVitePlugin } from '@nuxt/kit'; import { colors } from 'consola/utils'; import { join, dirname as dirname$1, resolve } from 'pathe'; import sirv from 'sirv'; import { searchForWorkspaceRoot } from 'vite'; import { d as defaultAllowedExtensions, a as defaultTabOptions, W as WS_EVENT_NAME } from '../shared/devtools.68e43ed6.mjs'; import { dirname, join as join$1, relative, parse } from 'node:path'; import { randomStr } from '@antfu/utils'; import { hash } from 'ohash'; import { runtimeDir, clientDir, packageDir, isGlobalInstall } from '../dirs.mjs'; import { createBirpcGroup } from 'birpc'; import { stringify, parse as parse$1 } from 'flatted'; import { startSubprocess } from '@nuxt/devtools-kit'; import Git from 'simple-git'; import { glob } from 'tinyglobby'; import { imageMeta } from 'image-meta'; import { debounce } from 'perfect-debounce'; import destr from 'destr'; import { snakeCase } from 'scule'; import { resolveBuiltinPresets } from 'unimport'; import { setupHooksDebug } from '../../dist/runtime/shared/hooks.js'; import isInstalledGlobally from 'is-installed-globally'; import { parseModule } from 'magicast'; import { addNuxtModule, getDefaultExportOptions } from 'magicast/helpers'; import { detectPackageManager } from 'nypm'; import { createRequire } from 'node:module'; import { getPackageInfo } from 'local-pkg'; import 'pkg-types'; import semver from 'semver'; const version = "1.4.2"; function getHomeDir() { return process.env.XDG_CONFIG_HOME || homedir(); } async function readLocalOptions(defaults, options) { const { filePath } = getOptionsFilepath(options); if (existsSync(filePath)) { try { const options2 = { ...defaults, ...JSON.parse(await fs.readFile(filePath, "utf-8")).settings || {} }; return options2; } catch (e) { console.error(`[DevTools] failed to parse local options file: ${filePath}, fallback to defaults`); console.error(e); return { ...defaults }; } } else { return { ...defaults }; } } function getOptionsFilepath(options) { let hashedKey; if (options.key) hashedKey = hash(`${options.root}:${options.key}`); else hashedKey = hash(options.root); const home = getHomeDir(); const filePath = join(home, ".nuxt/devtools", `${hashedKey}.json`); return { filePath, hashedKey }; } async function clearLocalOptions(options) { const { filePath } = getOptionsFilepath(options); if (existsSync(filePath)) await fs.unlink(filePath); } async function writeLocalOptions(settings, options) { const { filePath, hashedKey } = getOptionsFilepath(options); await fs.mkdir(dirname(filePath), { recursive: true }); await fs.writeFile( filePath, JSON.stringify( { root: options.root, hash: hashedKey, settings }, null, 2 ), "utf-8" ); } let token; async function getDevAuthToken() { if (token) return token; const home = getHomeDir(); const dir = join$1(home, ".nuxt/devtools"); const filepath = join$1(dir, "dev-auth-token.txt"); if (existsSync(filepath)) token = (await fs.readFile(filepath, "utf-8")).trim(); if (!token) token = randomStr(16); await fs.mkdir(dir, { recursive: true }); await fs.writeFile(filepath, token, "utf-8"); return token; } function setupAnalyzeBuildRPC({ nuxt, refresh, ensureDevAuthToken }) { let builds = []; let promise; let initalized; const processId = "devtools:analyze-build"; const analyzeDir = join(nuxt.options.rootDir, ".nuxt/analyze"); async function startAnalyzeBuild(name) { if (promise) throw new Error("Already building"); const result = startSubprocess({ command: "npx", args: ["nuxi", "analyze", "--no-serve", "--name", name], cwd: nuxt.options.rootDir }, { id: processId, name: "Analyze Build", icon: "logos-nuxt-icon" }, nuxt); refresh("getAnalyzeBuildInfo"); promise = result.getProcess().then(() => { refresh("getAnalyzeBuildInfo"); return readBuildInfo(); }).finally(() => { promise = void 0; initalized = void 0; refresh("getAnalyzeBuildInfo"); }); return processId; } async function readBuildInfo() { const files = await glob(["*/meta.json"], { cwd: analyzeDir, onlyFiles: true, absolute: true }); builds = await Promise.all(files.map(async (file) => { const dir = dirname$1(file); const json = JSON.parse(await fs.readFile(file, "utf-8")); return { ...json, features: { bundleClient: fs$1.existsSync(join(dir, "client.html")), bundleNitro: fs$1.existsSync(join(dir, "nitro.html")), viteInspect: fs$1.existsSync(join(dir, ".vite-inspect")) } }; })); return builds.sort((a, b) => b.endTime - a.endTime); } async function generateAnalyzeBuildName() { try { const git = Git(nuxt.options.rootDir); const branch = await git.branch(); const branchName = branch.current || "head"; const sha = await git.revparse(["--short", "HEAD"]); const isWorkingTreeClean = (await git.status()).isClean(); if (isWorkingTreeClean) return `${branchName}#${sha}`; return `${branchName}#${sha}-dirty`; } catch { return (/* @__PURE__ */ new Date()).toISOString().replace(/:/g, "-"); } } return { async getAnalyzeBuildInfo() { if (!initalized) initalized = readBuildInfo(); await initalized; return { isBuilding: !!promise, builds }; }, async clearAnalyzeBuilds(token, names) { await ensureDevAuthToken(token); if (!names) { await fs.rm(analyzeDir, { recursive: true, force: true }); } else { const targets = builds.filter((build) => names.includes(build.name)); await Promise.all(targets.map((target) => fs.rm(join(analyzeDir, target.slug), { recursive: true, force: true }))); } initalized = readBuildInfo(); refresh("getAnalyzeBuildInfo"); }, generateAnalyzeBuildName, async startAnalyzeBuild(token, ...args) { await ensureDevAuthToken(token); return startAnalyzeBuild(...args); } }; } function setupAssetsRPC({ nuxt, ensureDevAuthToken, refresh, options }) { const _imageMetaCache = /* @__PURE__ */ new Map(); let cache = null; const extensions = options.assets?.uploadExtensions || defaultAllowedExtensions; const publicDir = resolve(nuxt.options.srcDir, nuxt.options.dir.public); const layerDirs = [publicDir, ...nuxt.options._layers.map((layer) => resolve(layer.cwd, "public"))]; const refreshDebounced = debounce(() => { cache = null; refresh("getStaticAssets"); }, 500); nuxt.hook("builder:watch", (event, key) => { key = relative(nuxt.options.srcDir, resolve(nuxt.options.srcDir, key)); if (key.startsWith(nuxt.options.dir.public) && (event === "add" || event === "unlink")) refreshDebounced(); }); async function scan() { if (cache) return cache; const baseURL = nuxt.options.app.baseURL; const dirs = []; for (const layerDir of layerDirs) { const files = await glob(["**/*"], { cwd: layerDir, onlyFiles: true }); dirs.push({ layerDir, files }); } const uniquePaths = /* @__PURE__ */ new Set(); cache = []; for (const { layerDir, files } of dirs) { for (const path of files) { const filePath = resolve(layerDir, path); const stat = await fs.lstat(filePath); const fullPath = join(baseURL, path); if (!uniquePaths.has(fullPath)) { cache.push({ path, publicPath: fullPath, filePath, type: guessType(path), size: stat.size, mtime: stat.mtimeMs, layer: publicDir !== layerDir ? layerDir : void 0 }); uniquePaths.add(fullPath); } } } return cache.sort((a, b) => a.path.localeCompare(b.path)); } return { async getStaticAssets() { return await scan(); }, async getImageMeta(token, filepath) { await ensureDevAuthToken(token); if (_imageMetaCache.has(filepath)) return _imageMetaCache.get(filepath); try { const meta = imageMeta(await fs.readFile(filepath)); _imageMetaCache.set(filepath, meta); return meta; } catch (e) { _imageMetaCache.set(filepath, void 0); console.error(e); return void 0; } }, async getTextAssetContent(token, filepath, limit = 300) { await ensureDevAuthToken(token); try { const content = await fs.readFile(filepath, "utf-8"); return content.slice(0, limit); } catch (e) { console.error(e); return void 0; } }, async writeStaticAssets(token, files, folder) { await ensureDevAuthToken(token); const baseDir = resolve(nuxt.options.srcDir, nuxt.options.dir.public + folder); return await Promise.all( files.map(async ({ path, content, encoding, override }) => { let finalPath = resolve(baseDir, path); const { ext } = parse(finalPath); if (extensions !== "*") { if (!extensions.includes(ext.toLowerCase().slice(1))) throw new Error(`File extension ${ext} is not allowed to upload, allowed extensions are: ${extensions.join(", ")} You can configure it in Nuxt config at \`devtools.assets.uploadExtensions\`.`); } if (!override) { try { await fs.stat(finalPath); const base = finalPath.slice(0, finalPath.length - ext.length - 1); let i = 1; while (await fs.access(`${base}-${i}.${ext}`).then(() => true).catch(() => false)) i++; finalPath = `${base}-${i}.${ext}`; } catch { } } await fs.writeFile(finalPath, content, { encoding: encoding ?? "utf-8" }); return finalPath; }) ); }, async deleteStaticAsset(token, path) { await ensureDevAuthToken(token); return await fs.unlink(path); }, async renameStaticAsset(token, oldPath, newPath) { await ensureDevAuthToken(token); const exist = cache?.find((asset) => asset.filePath === newPath); if (exist) throw new Error(`File ${newPath} already exists`); return await fs.rename(oldPath, newPath); } }; } const reImage = /\.(?:png|jpe?g|jxl|gif|svg|webp|avif|ico|bmp|tiff?)$/i; const reVideo = /\.(?:mp4|webm|ogv|mov|avi|flv|wmv|mpg|mpeg|mkv|3gp|3g2|ts|mts|m2ts|vob|ogm|ogx|rm|rmvb|asf|amv|divx|m4v|svi|viv|f4v|f4p|f4a|f4b)$/i; const reAudio = /\.(?:mp3|wav|ogg|flac|aac|wma|alac|ape|ac3|dts|tta|opus|amr|aiff|au|mid|midi|ra|rm|wv|weba|dss|spx|vox|tak|dsf|dff|dsd|cda)$/i; const reFont = /\.(?:woff2?|eot|ttf|otf|ttc|pfa|pfb|pfm|afm)/i; const reText = /\.(?:json[5c]?|te?xt|[mc]?[jt]sx?|md[cx]?|markdown)/i; function guessType(path) { if (reImage.test(path)) return "image"; if (reVideo.test(path)) return "video"; if (reAudio.test(path)) return "audio"; if (reFont.test(path)) return "font"; if (reText.test(path)) return "text"; return "other"; } function setupCustomTabRPC({ nuxt, options, refresh }) { const iframeTabs = []; const customTabs = []; if (options.customTabs?.length) customTabs.push(...options.customTabs); async function initHooks() { nuxt.hook("devtools:customTabs:refresh", initCustomTabs); await initCustomTabs(); } async function initCustomTabs() { customTabs.length = 0; if (options.customTabs?.length) customTabs.push(...options.customTabs); await nuxt.callHook("devtools:customTabs", customTabs); refresh("getCustomTabs"); } nuxt.hook("app:resolve", async () => { await initHooks(); }); return { getCustomTabs() { return [ ...iframeTabs, ...customTabs ].map((i) => { i.category = i.category || "modules"; return i; }); }, async customTabAction(name, actionIndex) { const tab = customTabs.find((i) => i.name === name); if (!tab) return false; const view = tab.view; if (view.type !== "launch") return false; const action = view.actions?.[actionIndex]; if (!action) return false; Promise.resolve(action.handle?.()).catch((e) => { console.error(e); }).finally(() => { nuxt.callHook("devtools:customTabs:refresh"); }); nuxt.callHook("devtools:customTabs:refresh"); return true; } }; } function setupGeneralRPC({ nuxt, options, refresh, ensureDevAuthToken, openInEditorHooks }) { const components = []; const imports = []; const importPresets = []; let importDirs = []; const serverPages = []; let serverApp; const serverHooks = setupHooksDebug(nuxt.hooks); let unimport; let app; nuxt.hook("components:extend", (v) => { components.length = 0; components.push(...v); components.sort((a, b) => a.pascalName.localeCompare(b.pascalName)); refresh("getComponents"); }); nuxt.hook("imports:extend", (v) => { imports.length = 0; imports.push(...v); refresh("getAutoImports"); }); nuxt.hook("pages:extend", (v) => { serverPages.length = 0; const pagesSet = new Set(v); function searchChildren(page) { if (pagesSet.has(page)) return; pagesSet.add(page); page.children?.forEach(searchChildren); } v.forEach(searchChildren); serverPages.push(...Array.from(pagesSet).sort((a, b) => a.path.localeCompare(b.path))); refresh("getServerPages"); }); nuxt.hook("app:resolve", (app2) => { serverApp = app2; }); nuxt.hook("imports:sources", async (v) => { const result = (await resolveBuiltinPresets(v)).flat(); importPresets.length = 0; importPresets.push(...result); refresh("getAutoImports"); }); nuxt.hook("imports:context", (_unimport) => { unimport = _unimport; }); nuxt.hook("imports:dirs", (dirs) => { importDirs = dirs; }); nuxt.hook("app:resolve", (v) => { app = v; }); return { getServerConfig() { return nuxt.options; }, getServerRuntimeConfig() { const ENV_PREFIX = "NITRO_"; const ENV_PREFIX_ALT = "NUXT_"; function _getEnv(key) { const envKey = snakeCase(key).toUpperCase(); return destr(process.env[ENV_PREFIX + envKey] ?? process.env[ENV_PREFIX_ALT + envKey]); } function _isObject(input) { return typeof input === "object" && !Array.isArray(input); } function _applyEnv(obj, parentKey = "") { for (const key in obj) { const subKey = parentKey ? `${parentKey}_${key}` : key; const envValue = _getEnv(subKey); if (_isObject(obj[key])) { if (_isObject(envValue)) obj[key] = { ...obj[key], ...envValue }; _applyEnv(obj[key], subKey); } else { obj[key] = envValue ?? obj[key]; } } return obj; } const runtime = { ...nuxt.options.runtimeConfig }; _applyEnv(runtime); return runtime; }, getModuleOptions() { return options; }, getServerApp() { return serverApp; }, getComponents() { return components; }, async getComponentsRelationships() { return []; }, getServerPages() { return serverPages; }, getAutoImports() { return { imports: [ ...imports, ...importPresets ], metadata: unimport?.getMetadata(), dirs: importDirs }; }, getServerLayouts() { return Object.values(app?.layouts || []); }, getServerHooks() { return Object.values(serverHooks); }, async openInEditor(input) { if (input.startsWith("./")) input = resolve(process.cwd(), input); const match = input.match(/^(.*?)(:[:\d]*)$/); let suffix = ""; if (match) { input = match[1]; suffix = match[2]; } const path = [ input, `${input}.js`, `${input}.mjs`, `${input}.ts` ].find((i) => existsSync(i)); if (!path) { console.error("File not found:", input); return false; } try { for (const hook of openInEditorHooks) { const result = await hook(path); if (result) return true; } await import('launch-editor').then((r) => (r.default || r)(path + suffix)); return true; } catch (e) { console.error(e); return false; } }, async restartNuxt(token, hard = true) { await ensureDevAuthToken(token); logger.info("Restarting Nuxt..."); return nuxt.callHook("restart", { hard }); }, async requestForAuth(info, origin) { if (options.disableAuthorization) return; const token = await getDevAuthToken(); origin ||= `${nuxt.options.devServer.https ? "https" : "http"}://${nuxt.options.devServer.host === "::" ? "localhost" : nuxt.options.devServer.host || "localhost"}:${nuxt.options.devServer.port}`; const ROUTE_AUTH = `${nuxt.options.app.baseURL || "/"}/__nuxt_devtools__/auth`.replace(/\/+/g, "/"); const message = [ `A browser is requesting permissions of ${colors.bold(colors.yellow("writing files and running commands"))} from the DevTools UI.`, colors.bold(info || "Unknown"), "", "Please open the following URL in the browser:", colors.bold(colors.green(`${origin}${ROUTE_AUTH}?token=${token}`)), "", "Or manually copy and paste the following token:", colors.bold(colors.cyan(token)) ]; logger.box({ message: message.join("\n"), title: colors.bold(colors.yellow(" Permission Request ")), style: { borderColor: "yellow", borderStyle: "rounded" } }); }, async verifyAuthToken(token) { if (options.disableAuthorization) return true; return token === await getDevAuthToken(); } }; } const HASH_RE = /#/g; const AMPERSAND_RE = /&/g; const SLASH_RE = /\//g; const EQUAL_RE = /=/g; const PLUS_RE = /\+/g; const ENC_CARET_RE = /%5e/gi; const ENC_BACKTICK_RE = /%60/gi; const ENC_PIPE_RE = /%7c/gi; const ENC_SPACE_RE = /%20/gi; function encode(text) { return encodeURI("" + text).replace(ENC_PIPE_RE, "|"); } function encodeQueryValue(input) { return encode(typeof input === "string" ? input : JSON.stringify(input)).replace(PLUS_RE, "%2B").replace(ENC_SPACE_RE, "+").replace(HASH_RE, "%23").replace(AMPERSAND_RE, "%26").replace(ENC_BACKTICK_RE, "`").replace(ENC_CARET_RE, "^").replace(SLASH_RE, "%2F"); } function encodeQueryKey(text) { return encodeQueryValue(text).replace(EQUAL_RE, "%3D"); } function decode(text = "") { try { return decodeURIComponent("" + text); } catch { return "" + text; } } function decodeQueryKey(text) { return decode(text.replace(PLUS_RE, " ")); } function decodeQueryValue(text) { return decode(text.replace(PLUS_RE, " ")); } function parseQuery(parametersString = "") { const object = {}; if (parametersString[0] === "?") { parametersString = parametersString.slice(1); } for (const parameter of parametersString.split("&")) { const s = parameter.match(/([^=]+)=?(.*)/) || []; if (s.length < 2) { continue; } const key = decodeQueryKey(s[1]); if (key === "__proto__" || key === "constructor") { continue; } const value = decodeQueryValue(s[2] || ""); if (object[key] === void 0) { object[key] = value; } else if (Array.isArray(object[key])) { object[key].push(value); } else { object[key] = [object[key], value]; } } return object; } function encodeQueryItem(key, value) { if (typeof value === "number" || typeof value === "boolean") { value = String(value); } if (!value) { return encodeQueryKey(key); } if (Array.isArray(value)) { return value.map((_value) => `${encodeQueryKey(key)}=${encodeQueryValue(_value)}`).join("&"); } return `${encodeQueryKey(key)}=${encodeQueryValue(value)}`; } function stringifyQuery(query) { return Object.keys(query).filter((k) => query[k] !== void 0).map((k) => encodeQueryItem(k, query[k])).filter(Boolean).join("&"); } const PROTOCOL_STRICT_REGEX = /^[\s\w\0+.-]{2,}:([/\\]{1,2})/; const PROTOCOL_REGEX = /^[\s\w\0+.-]{2,}:([/\\]{2})?/; const PROTOCOL_RELATIVE_REGEX = /^([/\\]\s*){2,}[^/\\]/; const TRAILING_SLASH_RE = /\/$|\/\?|\/#/; const JOIN_LEADING_SLASH_RE = /^\.?\//; function hasProtocol(inputString, opts = {}) { if (typeof opts === "boolean") { opts = { acceptRelative: opts }; } if (opts.strict) { return PROTOCOL_STRICT_REGEX.test(inputString); } return PROTOCOL_REGEX.test(inputString) || (opts.acceptRelative ? PROTOCOL_RELATIVE_REGEX.test(inputString) : false); } function hasTrailingSlash(input = "", respectQueryAndFragment) { if (!respectQueryAndFragment) { return input.endsWith("/"); } return TRAILING_SLASH_RE.test(input); } function withoutTrailingSlash(input = "", respectQueryAndFragment) { if (!respectQueryAndFragment) { return (hasTrailingSlash(input) ? input.slice(0, -1) : input) || "/"; } if (!hasTrailingSlash(input, true)) { return input || "/"; } let path = input; let fragment = ""; const fragmentIndex = input.indexOf("#"); if (fragmentIndex >= 0) { path = input.slice(0, fragmentIndex); fragment = input.slice(fragmentIndex); } const [s0, ...s] = path.split("?"); const cleanPath = s0.endsWith("/") ? s0.slice(0, -1) : s0; return (cleanPath || "/") + (s.length > 0 ? `?${s.join("?")}` : "") + fragment; } function withTrailingSlash(input = "", respectQueryAndFragment) { if (!respectQueryAndFragment) { return input.endsWith("/") ? input : input + "/"; } if (hasTrailingSlash(input, true)) { return input || "/"; } let path = input; let fragment = ""; const fragmentIndex = input.indexOf("#"); if (fragmentIndex >= 0) { path = input.slice(0, fragmentIndex); fragment = input.slice(fragmentIndex); if (!path) { return fragment; } } const [s0, ...s] = path.split("?"); return s0 + "/" + (s.length > 0 ? `?${s.join("?")}` : "") + fragment; } function withBase(input, base) { if (isEmptyURL(base) || hasProtocol(input)) { return input; } const _base = withoutTrailingSlash(base); if (input.startsWith(_base)) { return input; } return joinURL(_base, input); } function withQuery(input, query) { const parsed = parseURL(input); const mergedQuery = { ...parseQuery(parsed.search), ...query }; parsed.search = stringifyQuery(mergedQuery); return stringifyParsedURL(parsed); } function isEmptyURL(url) { return !url || url === "/"; } function isNonEmptyURL(url) { return url && url !== "/"; } function joinURL(base, ...input) { let url = base || ""; for (const segment of input.filter((url2) => isNonEmptyURL(url2))) { if (url) { const _segment = segment.replace(JOIN_LEADING_SLASH_RE, ""); url = withTrailingSlash(url) + _segment; } else { url = segment; } } return url; } const protocolRelative = Symbol.for("ufo:protocolRelative"); function parseURL(input = "", defaultProto) { const _specialProtoMatch = input.match( /^[\s\0]*(blob:|data:|javascript:|vbscript:)(.*)/i ); if (_specialProtoMatch) { const [, _proto, _pathname = ""] = _specialProtoMatch; return { protocol: _proto.toLowerCase(), pathname: _pathname, href: _proto + _pathname, auth: "", host: "", search: "", hash: "" }; } if (!hasProtocol(input, { acceptRelative: true })) { return defaultProto ? parseURL(defaultProto + input) : parsePath(input); } const [, protocol = "", auth, hostAndPath = ""] = input.replace(/\\/g, "/").match(/^[\s\0]*([\w+.-]{2,}:)?\/\/([^/@]+@)?(.*)/) || []; let [, host = "", path = ""] = hostAndPath.match(/([^#/?]*)(.*)?/) || []; if (protocol === "file:") { path = path.replace(/\/(?=[A-Za-z]:)/, ""); } const { pathname, search, hash } = parsePath(path); return { protocol: protocol.toLowerCase(), auth: auth ? auth.slice(0, Math.max(0, auth.length - 1)) : "", host, pathname, search, hash, [protocolRelative]: !protocol }; } function parsePath(input = "") { const [pathname = "", search = "", hash = ""] = (input.match(/([^#?]*)(\?[^#]*)?(#.*)?/) || []).splice(1); return { pathname, search, hash }; } function stringifyParsedURL(parsed) { const pathname = parsed.pathname || ""; const search = parsed.search ? (parsed.search.startsWith("?") ? "" : "?") + parsed.search : ""; const hash = parsed.hash || ""; const auth = parsed.auth ? parsed.auth + "@" : ""; const host = parsed.host || ""; const proto = parsed.protocol || parsed[protocolRelative] ? (parsed.protocol || "") + "//" : ""; return proto + auth + host + pathname + search + hash; } class FetchError extends Error { constructor(message, opts) { super(message, opts); this.name = "FetchError"; if (opts?.cause && !this.cause) { this.cause = opts.cause; } } } function createFetchError(ctx) { const errorMessage = ctx.error?.message || ctx.error?.toString() || ""; const method = ctx.request?.method || ctx.options?.method || "GET"; const url = ctx.request?.url || String(ctx.request) || "/"; const requestStr = `[${method}] ${JSON.stringify(url)}`; const statusStr = ctx.response ? `${ctx.response.status} ${ctx.response.statusText}` : "<no response>"; const message = `${requestStr}: ${statusStr}${errorMessage ? ` ${errorMessage}` : ""}`; const fetchError = new FetchError( message, ctx.error ? { cause: ctx.error } : void 0 ); for (const key of ["request", "options", "response"]) { Object.defineProperty(fetchError, key, { get() { return ctx[key]; } }); } for (const [key, refKey] of [ ["data", "_data"], ["status", "status"], ["statusCode", "status"], ["statusText", "statusText"], ["statusMessage", "statusText"] ]) { Object.defineProperty(fetchError, key, { get() { return ctx.response && ctx.response[refKey]; } }); } return fetchError; } const payloadMethods = new Set( Object.freeze(["PATCH", "POST", "PUT", "DELETE"]) ); function isPayloadMethod(method = "GET") { return payloadMethods.has(method.toUpperCase()); } function isJSONSerializable(value) { if (value === void 0) { return false; } const t = typeof value; if (t === "string" || t === "number" || t === "boolean" || t === null) { return true; } if (t !== "object") { return false; } if (Array.isArray(value)) { return true; } if (value.buffer) { return false; } return value.constructor && value.constructor.name === "Object" || typeof value.toJSON === "function"; } const textTypes = /* @__PURE__ */ new Set([ "image/svg", "application/xml", "application/xhtml", "application/html" ]); const JSON_RE = /^application\/(?:[\w!#$%&*.^`~-]*\+)?json(;.+)?$/i; function detectResponseType(_contentType = "") { if (!_contentType) { return "json"; } const contentType = _contentType.split(";").shift() || ""; if (JSON_RE.test(contentType)) { return "json"; } if (textTypes.has(contentType) || contentType.startsWith("text/")) { return "text"; } return "blob"; } function mergeFetchOptions(input, defaults, Headers = globalThis.Headers) { const merged = { ...defaults, ...input }; if (defaults?.params && input?.params) { merged.params = { ...defaults?.params, ...input?.params }; } if (defaults?.query && input?.query) { merged.query = { ...defaults?.query, ...input?.query }; } if (defaults?.headers && input?.headers) { merged.headers = new Headers(defaults?.headers || {}); for (const [key, value] of new Headers(input?.headers || {})) { merged.headers.set(key, value); } } return merged; } const retryStatusCodes = /* @__PURE__ */ new Set([ 408, // Request Timeout 409, // Conflict 425, // Too Early 429, // Too Many Requests 500, // Internal Server Error 502, // Bad Gateway 503, // Service Unavailable 504 // Gateway Timeout ]); const nullBodyResponses = /* @__PURE__ */ new Set([101, 204, 205, 304]); function createFetch(globalOptions = {}) { const { fetch = globalThis.fetch, Headers = globalThis.Headers, AbortController = globalThis.AbortController } = globalOptions; async function onError(context) { const isAbort = context.error && context.error.name === "AbortError" && !context.options.timeout || false; if (context.options.retry !== false && !isAbort) { let retries; if (typeof context.options.retry === "number") { retries = context.options.retry; } else { retries = isPayloadMethod(context.options.method) ? 0 : 1; } const responseCode = context.response && context.response.status || 500; if (retries > 0 && (Array.isArray(context.options.retryStatusCodes) ? context.options.retryStatusCodes.includes(responseCode) : retryStatusCodes.has(responseCode))) { const retryDelay = context.options.retryDelay || 0; if (retryDelay > 0) { await new Promise((resolve) => setTimeout(resolve, retryDelay)); } return $fetchRaw(context.request, { ...context.options, retry: retries - 1 }); } } const error = createFetchError(context); if (Error.captureStackTrace) { Error.captureStackTrace(error, $fetchRaw); } throw error; } const $fetchRaw = async function $fetchRaw2(_request, _options = {}) { const context = { request: _request, options: mergeFetchOptions(_options, globalOptions.defaults, Headers), response: void 0, error: void 0 }; context.options.method = context.options.method?.toUpperCase(); if (context.options.onRequest) { await context.options.onRequest(context); } if (typeof context.request === "string") { if (context.options.baseURL) { context.request = withBase(context.request, context.options.baseURL); } if (context.options.query || context.options.params) { context.request = withQuery(context.request, { ...context.options.params, ...context.options.query }); } } if (context.options.body && isPayloadMethod(context.options.method)) { if (isJSONSerializable(context.options.body)) { context.options.body = typeof context.options.body === "string" ? context.options.body : JSON.stringify(context.options.body); context.options.headers = new Headers(context.options.headers || {}); if (!context.options.headers.has("content-type")) { context.options.headers.set("content-type", "application/json"); } if (!context.options.headers.has("accept")) { context.options.headers.set("accept", "application/json"); } } else if ( // ReadableStream Body "pipeTo" in context.options.body && typeof context.options.body.pipeTo === "function" || // Node.js Stream Body typeof context.options.body.pipe === "function" ) { if (!("duplex" in context.options)) { context.options.duplex = "half"; } } } let abortTimeout; if (!context.options.signal && context.options.timeout) { const controller = new AbortController(); abortTimeout = setTimeout( () => controller.abort(), context.options.timeout ); context.options.signal = controller.signal; } try { context.response = await fetch( context.request, context.options ); } catch (error) { context.error = error; if (context.options.onRequestError) { await context.options.onRequestError(context); } return await onError(context); } finally { if (abortTimeout) { clearTimeout(abortTimeout); } } const hasBody = context.response.body && !nullBodyResponses.has(context.response.status) && context.options.method !== "HEAD"; if (hasBody) { const responseType = (context.options.parseResponse ? "json" : context.options.responseType) || detectResponseType(context.response.headers.get("content-type") || ""); switch (responseType) { case "json": { const data = await context.response.text(); const parseFunction = context.options.parseResponse || destr; context.response._data = parseFunction(data); break; } case "stream": { context.response._data = context.response.body; break; } default: { context.response._data = await context.response[responseType](); } } } if (context.options.onResponse) { await context.options.onResponse(context); } if (!context.options.ignoreResponseError && context.response.status >= 400 && context.response.status < 600) { if (context.options.onResponseError) { await context.options.onResponseError(context); } return await onError(context); } return context.response; }; const $fetch = async function $fetch2(request, options) { const r = await $fetchRaw(request, options); return r._data; }; $fetch.raw = $fetchRaw; $fetch.native = (...args) => fetch(...args); $fetch.create = (defaultOptions = {}) => createFetch({ ...globalOptions, defaults: { ...globalOptions.defaults, ...defaultOptions } }); return $fetch; } const _globalThis = function() { if (typeof globalThis !== "undefined") { return globalThis; } if (typeof self !== "undefined") { return self; } if (typeof window !== "undefined") { return window; } if (typeof global !== "undefined") { return global; } throw new Error("unable to locate global object"); }(); const fetch = _globalThis.fetch || (() => Promise.reject(new Error("[ofetch] global.fetch is not supported!"))); const Headers = _globalThis.Headers; const AbortController = _globalThis.AbortController; createFetch({ fetch, Headers, AbortController }); async function checkForUpdateOf(name, current, nuxt = useNuxt()) { try { if (!current) { const require = createRequire(nuxt.options.rootDir); const info = await getPackageInfo(name, { paths: require.resolve.paths(name) || void 0 }); if (!info) return; current = info.packageJson.version; } if (!current) return; const { getLatestVersion } = await import('fast-npm-meta'); const { version: latest } = await getLatestVersion(name, { fetch }); const needsUpdate = !!latest && latest !== current && semver.lt(current, latest); return { name, current, latest: latest || current, needsUpdate }; } catch (e) { logger.warn(`Failed to check for update of ${name}:`); logger.warn(e); } } async function magicastGuard(fn, message = "") { let generated; try { generated = await fn(); } catch (e) { logger.error(e); throw new Error(`Magicast failed to modify Nuxt config automatically. Maybe the config are composed too dynamically that we failed to statically analyze it. ${message}`); } return generated; } function setupNpmRPC({ nuxt, ensureDevAuthToken }) { let detectPromise; const updatesPromise = /* @__PURE__ */ new Map(); function getPackageManager() { detectPromise ||= detectPackageManager(nuxt.options.rootDir); return detectPromise; } async function getNpmCommand(command, packageName, options = {}) { const { dev = true, global = packageName === "@nuxt/devtools" && isInstalledGlobally } = options; const agent = await getPackageManager(); const name = agent?.name || "npm"; if (command === "install" || command === "update") { return [ name, name === "npm" ? "install" : "add", `${packageName}@latest`, dev ? "-D" : "", global ? "-g" : "", // In yarn berry, `--ignore-scripts` is removed name === "yarn" && !agent?.version?.startsWith("1.") ? "" : "--ignore-scripts" ].filter(Boolean); } if (command === "uninstall") { return [ name, name === "npm" ? "uninstall" : "remove", packageName, global ? "-g" : "" ].filter(Boolean); } } async function runNpmCommand(command, packageName, options = {}) { const args = await getNpmCommand(command, packageName, options); if (!args) return; const processId = `npm:${command}:${packageName}`; startSubprocess({ command: args[0], args: args.slice(1) }, { id: processId, name: `${command} ${packageName}`, icon: "i-logos-npm-icon", restartable: false }); return { processId }; } const installSet = /* @__PURE__ */ new Set(); let latestGenerated = null; return { checkForUpdateFor(name) { if (!updatesPromise.has(name)) updatesPromise.set(name, checkForUpdateOf(name, void 0, nuxt)); return updatesPromise.get(name); }, getNpmCommand, async runNpmCommand(token, ...args) { await ensureDevAuthToken(token); return runNpmCommand(...args); }, async installNuxtModule(token, name, dry = true) { await ensureDevAuthToken(token); const commands = await getNpmCommand("install", name, { dev: true }); const filepath = nuxt.options._nuxtConfigFile; let source = latestGenerated; if (source == null) source = await fs.readFile(filepath, "utf-8"); const generated = await magicastGuard(async () => { const mod = parseModule(source, { sourceFileName: filepath }); addNuxtModule(mod, name); return mod.generate().code; }); const processId = `nuxt:add-module:${name}`; if (!dry) { latestGenerated = generated; installSet.add(name); const process = startSubprocess({ command: commands[0], args: commands.slice(1) }, { id: processId, name: `Install ${name}`, icon: "carbon:intent-request-create", restartable: false }); const execa = process.getProcess(); const result = await execa; await Promise.resolve(); installSet.delete(name); const code = result.exitCode; if (code !== 0) { console.error(result.stderr); throw new Error(`Failed to install module, process exited with ${code}`); } if (installSet.size === 0) { latestGenerated = null; await fs.writeFile(filepath, generated, "utf-8"); } } return { configOriginal: source, configGenerated: generated, commands, processId }; }, async uninstallNuxtModule(token, name, dry = true) { await ensureDevAuthToken(token); const commands = await getNpmCommand("uninstall", name); const filepath = nuxt.options._nuxtConfigFile; const source = await fs.readFile(filepath, "utf-8"); const generated = await magicastGuard(async () => { const mod = parseModule(source, { sourceFileName: filepath }); const config = getDefaultExportOptions(mod); config.modules ||= []; if (config.modules.includes(name)) { Object.values(config.modules).forEach((value, index) => { if (value === name) config.modules.splice(index - 1, 1); }); } return mod.generate().code; }); const processId = `nuxt:remove-module:${name}`; if (!dry) { const process = startSubprocess({ command: commands[0], args: commands.slice(1) }, { id: processId, name: `Uninstall ${name}`, icon: "carbon:intent-request-uninstall", restartable: false }); const execa = process.getProcess(); const result = await execa; await Promise.resolve(); const code = result.exitCode; if (code !== 0) { console.error(result.stderr); throw new Error(`Failed to uninstall module', process exited with ${code}`); } await fs.writeFile(filepath, generated, "utf-8"); } return { configOriginal: source, configGenerated: generated, commands, processId }; } }; } let options; function getOptions() { return options; } function setupOptionsRPC({ nuxt }) { async function getOptions2(tab) { if (!options || options[tab]) { options = defaultTabOptions; await read(tab); } return options[tab]; } async function read(tab) { options[tab] = await readLocalOptions(defaultTabOptions[tab], { root: nuxt.options.rootDir, key: tab !== "ui" && tab }); return options; } getOptions2("ui"); async function clearOptions() { options = void 0; await clearLocalOptions({ root: nuxt.options.rootDir }); } return { async updateOptions(tab, _settings) { const settings = await getOptions2(tab); Object.assign(settings, _settings); await writeLocalOptions( { ...settings }, { root: nuxt.options.rootDir, key: tab !== "ui" && tab } ); nuxt.callHook("builder:generateApp", { filter(template) { return template.filename.includes("devtools/settings.js"); } }); }, getOptions: getOptions2, clearOptions }; } function setupServerRoutesRPC({ nuxt, refresh }) { let nitro; let cache = null; const refreshDebounced = debounce(() => { cache = null; refresh("getServerRoutes"); }, 500); nuxt.hook("nitro:init", (_) => { nitro = _; cache = null; refresh("getServerRoutes"); }); nuxt.hook("ready", () => { nitro?.storage.watch((event, key) => { if (key.startsWith("src:api:") || key.startsWith("src:routes:")) refreshDebounced(); }); }); function scan() { if (cache) return cache; cache = (() => { if (!nitro) return []; return [ ...nitro.scannedHandlers.filter((item) => !item.middleware).map((item) => ({ route: item.route, filepath: item.handler, method: item.method, type: item.route?.startsWith("/api") ? "api" : "route" })), ...nitro.options.handlers.filter((item) => !item.route?.startsWith("/_nitro") && !item.route?.startsWith("/__nuxt") && !item.middleware).map((item) => ({ route: item.route, filepath: item.handler, method: item.method, type: "runtime" })) ]; })(); return cache; } return { getServerRoutes() { return scan(); } }; } function setupServerTasksRPC({ nuxt, refresh }) { let nitro; let cache = null; const refreshDebounced = debounce(() => { cache = null; refresh("getServerTasks"); }, 500); nuxt.hook("nitro:init", (_) => { nitro = _; cache = null; refresh("getServerTasks"); }); nuxt.hook("ready", () => { nitro?.storage.watch((event, key) => { if (key.startsWith("src:tasks:")) refreshDebounced(); }); }); function scan() { if (cache) return cache; cache = (() => { if (!nitro) { return { tasks: {}, scheduledTasks: {} }; } return { tasks: nitro.options.tasks, scheduledTasks: Object.entries(nitro.options.scheduledTasks ?? {}).reduce((acc, [cron, tasks]) => { acc[cron] = Array.isArray(tasks) ? tasks : [tasks]; return acc; }, {}) }; })(); return cache; } return { getServerTasks() { return scan(); } }; } const IGNORE_STORAGE_MOUNTS = ["root", "build", "src", "cache"]; function shouldIgnoreStorageKey(key) { return IGNORE_STORAGE_MOUNTS.includes(key.split(":")[0]); } function setupStorageRPC({ nuxt, rpc, ensureDevAuthToken }) { const storageMounts = {}; let storage; nuxt.hook("nitro:init", (nitro) => { storage = nitro.storage; nuxt.hook("ready", () => { storage.watch((event, key) => { if (shouldIgnoreStorageKey(key)) return; rpc.broadcast.callHook.asEvent("storage:key:update", key, event); }); }); const mounts = { ...nitro.options.storage, ...nitro.options.devStorage }; for (const name of Object.keys(mounts)) { if (shouldIgnoreStorageKey(name)) continue; storageMounts[name] = mounts[name]; } }); return { async getStorageMounts() { return storageMounts; }, async getStorageKeys(base) { if (!storage) return []; try { const keys = await storage.getKeys(base); return keys.filter((key) => !shouldIgnoreStorageKey(key)); } catch (err) { console.error(`Cloud not fetch storage keys for ${base}:`, err); return []; } }, async getStorageItem(token, key) { await ensureDevAuthToken(token); if (!storage) return null; return await storage.getItem(key); }, async setStorageItem(token, key, value) { await ensureDevAuthToken(token); if (!storage) return; return await storage.setItem(key, value); }, async removeStorageItem(token, key) { await ensureDevAuthToken(token); if (!storage) return; return await storage.removeItem(key); } }; } const SEND_DELAY = 5e3; function throttle(fn, delay) { let timer; return () => { if (!timer) { timer = setTimeout(() => { timer = void 0; fn(); }, delay); } }; } let telemetry; const throttledSend = throttle(() => { telemetry?.sendEvents(); }, SEND_DELAY); function setupTelemetryRPC({ nuxt, options }) { if (options.telemetry !== false) { nuxt.hook("telemetry:setup", (t) => { telemetry = t; t.eventFactories.devtools = (_, payload) => { return { name: "devtools", version, ...payload }; }; t.createEvent("devtools", { event: "enabled" }); }); } return { telemetryEvent(payload, immediate = false) { telemetryEvent(payload, immediate); } }; } function telemetryEvent(payload, immediate = false) { if (!telemetry) return; if (getOptions()?.behavior.telemetry === false) return; telemetry.createEvent("devtools", payload); if (immediate) telemetry.sendEvents(); else throttledSend(); } function setupTerminalRPC({ nuxt, rpc, refresh, ensureDevAuthToken }) { const terminals = /* @__PURE__ */ new Map(); nuxt.hook("devtools:terminal:register", (terminal) => { terminals.set(terminal.id, terminal); refresh("getTerminals"); return terminal.id; }); nuxt.hook("devtools:terminal:remove", ({ id }) => { if (!terminals.has(id)) return false; terminals.delete(id); refresh("getTerminals"); return true; }); nuxt.hook("devtools:terminal:write", ({ id, data }) => { const terminal = terminals.get(id); if (!terminal) return false; terminal.buffer ||= ""; terminal.buffer += data; rpc.broadcast.onTerminalData.asEvent({ id, data }); return true; }); nuxt.hook("devtools:terminal:exit", ({ id, code }) => { const terminal = terminals.get(id); if (!terminal) return false; terminal.isTerminated = true; rpc.broadcast.onTerminalExit.asEvent({ id, code }); refresh("getTerminals"); return true; }); function serializeTerminal(terminal, buffer = false) { if (!terminal) return; return { id: terminal.id, name: terminal.name, description: terminal.description, icon: terminal.icon, terminatable: terminal.terminatable ?? !!terminal.onActionTerminate, restartable: terminal.restartable ?? !!terminal.onActionRestart, isTerminated: terminal.isTerminated, buffer: buffer ? terminal.buffer : void 0 }; } return { getTerminals() { return Array.from(terminals.values()).map((i) => serializeTerminal(i)); }, async getTerminalDetail(token, id) { await ensureDevAuthToken(token); return serializeTerminal(terminals.get(id), true); }, async runTerminalAction(token, id, action) { await ensureDevAuthToken(token); const terminal = terminals.get(id); if (!terminal) return false; switch (action) { case "restart": if (!terminal.onActionRestart) return false; await terminal.onActionRestart(); return true; case "terminate": if (!terminal.onActionTerminate) return false; await terminal.onActionTerminate(); return true; case "remove": if (!terminal.isTerminated) terminal.onActionTerminate?.(); terminals.delete(id); refresh("getTerminals"); return true; case "clear": terminal.buffer = ""; refresh("getTerminals"); return true; } } }; } function setupTimelineRPC({ nuxt }) { return { async enableTimeline(dry) { const filepath = nuxt.options