diff --git a/.drone.yaml b/.drone.yaml index c409093..fbe1edd 100644 --- a/.drone.yaml +++ b/.drone.yaml @@ -10,7 +10,7 @@ steps: - name: build image: docker privileged: true - environment: + environment: TOKEN: from_secret: registry_token commands: @@ -18,4 +18,4 @@ steps: - docker login -u registry -p $TOKEN registry.v0l.io - docker build -t registry.v0l.io/lnvps-web:latest . - docker push registry.v0l.io/lnvps-web:latest - - kill $(cat /var/run/docker.pid) \ No newline at end of file + - kill $(cat /var/run/docker.pid) diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 8e8adf9..875f63d 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,6 +1,3 @@ { - "recommendations": [ - "arcanis.vscode-zipfs", - "dbaeumer.vscode-eslint" - ] + "recommendations": ["arcanis.vscode-zipfs", "dbaeumer.vscode-eslint"] } diff --git a/.yarn/sdks/eslint/bin/eslint.js b/.yarn/sdks/eslint/bin/eslint.js index e6604ff..794dc03 100755 --- a/.yarn/sdks/eslint/bin/eslint.js +++ b/.yarn/sdks/eslint/bin/eslint.js @@ -1,9 +1,9 @@ #!/usr/bin/env node -const {existsSync} = require(`fs`); -const {createRequire, register} = require(`module`); -const {resolve} = require(`path`); -const {pathToFileURL} = require(`url`); +const { existsSync } = require(`fs`); +const { createRequire, register } = require(`module`); +const { resolve } = require(`path`); +const { pathToFileURL } = require(`url`); const relPnpApiPath = "../../../../.pnp.cjs"; @@ -25,8 +25,8 @@ if (existsSync(absPnpApiPath)) { } const wrapWithUserWrapper = existsSync(absUserWrapperPath) - ? exports => absRequire(absUserWrapperPath)(exports) - : exports => exports; + ? (exports) => absRequire(absUserWrapperPath)(exports) + : (exports) => exports; // Defer to the real eslint/bin/eslint.js your application uses module.exports = wrapWithUserWrapper(absRequire(`eslint/bin/eslint.js`)); diff --git a/.yarn/sdks/eslint/lib/api.js b/.yarn/sdks/eslint/lib/api.js index 8addf97..52ddc80 100644 --- a/.yarn/sdks/eslint/lib/api.js +++ b/.yarn/sdks/eslint/lib/api.js @@ -1,9 +1,9 @@ #!/usr/bin/env node -const {existsSync} = require(`fs`); -const {createRequire, register} = require(`module`); -const {resolve} = require(`path`); -const {pathToFileURL} = require(`url`); +const { existsSync } = require(`fs`); +const { createRequire, register } = require(`module`); +const { resolve } = require(`path`); +const { pathToFileURL } = require(`url`); const relPnpApiPath = "../../../../.pnp.cjs"; @@ -25,8 +25,8 @@ if (existsSync(absPnpApiPath)) { } const wrapWithUserWrapper = existsSync(absUserWrapperPath) - ? exports => absRequire(absUserWrapperPath)(exports) - : exports => exports; + ? (exports) => absRequire(absUserWrapperPath)(exports) + : (exports) => exports; // Defer to the real eslint your application uses module.exports = wrapWithUserWrapper(absRequire(`eslint`)); diff --git a/.yarn/sdks/eslint/lib/unsupported-api.js b/.yarn/sdks/eslint/lib/unsupported-api.js index c2b464c..1d31bce 100644 --- a/.yarn/sdks/eslint/lib/unsupported-api.js +++ b/.yarn/sdks/eslint/lib/unsupported-api.js @@ -1,9 +1,9 @@ #!/usr/bin/env node -const {existsSync} = require(`fs`); -const {createRequire, register} = require(`module`); -const {resolve} = require(`path`); -const {pathToFileURL} = require(`url`); +const { existsSync } = require(`fs`); +const { createRequire, register } = require(`module`); +const { resolve } = require(`path`); +const { pathToFileURL } = require(`url`); const relPnpApiPath = "../../../../.pnp.cjs"; @@ -25,8 +25,8 @@ if (existsSync(absPnpApiPath)) { } const wrapWithUserWrapper = existsSync(absUserWrapperPath) - ? exports => absRequire(absUserWrapperPath)(exports) - : exports => exports; + ? (exports) => absRequire(absUserWrapperPath)(exports) + : (exports) => exports; // Defer to the real eslint/use-at-your-own-risk your application uses module.exports = wrapWithUserWrapper(absRequire(`eslint/use-at-your-own-risk`)); diff --git a/.yarn/sdks/typescript/bin/tsc b/.yarn/sdks/typescript/bin/tsc index 867a7bd..48f3391 100755 --- a/.yarn/sdks/typescript/bin/tsc +++ b/.yarn/sdks/typescript/bin/tsc @@ -1,9 +1,9 @@ #!/usr/bin/env node -const {existsSync} = require(`fs`); -const {createRequire, register} = require(`module`); -const {resolve} = require(`path`); -const {pathToFileURL} = require(`url`); +const { existsSync } = require(`fs`); +const { createRequire, register } = require(`module`); +const { resolve } = require(`path`); +const { pathToFileURL } = require(`url`); const relPnpApiPath = "../../../../.pnp.cjs"; @@ -25,8 +25,8 @@ if (existsSync(absPnpApiPath)) { } const wrapWithUserWrapper = existsSync(absUserWrapperPath) - ? exports => absRequire(absUserWrapperPath)(exports) - : exports => exports; + ? (exports) => absRequire(absUserWrapperPath)(exports) + : (exports) => exports; // Defer to the real typescript/bin/tsc your application uses module.exports = wrapWithUserWrapper(absRequire(`typescript/bin/tsc`)); diff --git a/.yarn/sdks/typescript/bin/tsserver b/.yarn/sdks/typescript/bin/tsserver index 3fc5aa3..03c654c 100755 --- a/.yarn/sdks/typescript/bin/tsserver +++ b/.yarn/sdks/typescript/bin/tsserver @@ -1,9 +1,9 @@ #!/usr/bin/env node -const {existsSync} = require(`fs`); -const {createRequire, register} = require(`module`); -const {resolve} = require(`path`); -const {pathToFileURL} = require(`url`); +const { existsSync } = require(`fs`); +const { createRequire, register } = require(`module`); +const { resolve } = require(`path`); +const { pathToFileURL } = require(`url`); const relPnpApiPath = "../../../../.pnp.cjs"; @@ -25,8 +25,8 @@ if (existsSync(absPnpApiPath)) { } const wrapWithUserWrapper = existsSync(absUserWrapperPath) - ? exports => absRequire(absUserWrapperPath)(exports) - : exports => exports; + ? (exports) => absRequire(absUserWrapperPath)(exports) + : (exports) => exports; // Defer to the real typescript/bin/tsserver your application uses module.exports = wrapWithUserWrapper(absRequire(`typescript/bin/tsserver`)); diff --git a/.yarn/sdks/typescript/lib/tsc.js b/.yarn/sdks/typescript/lib/tsc.js index da411bd..583a091 100644 --- a/.yarn/sdks/typescript/lib/tsc.js +++ b/.yarn/sdks/typescript/lib/tsc.js @@ -1,9 +1,9 @@ #!/usr/bin/env node -const {existsSync} = require(`fs`); -const {createRequire, register} = require(`module`); -const {resolve} = require(`path`); -const {pathToFileURL} = require(`url`); +const { existsSync } = require(`fs`); +const { createRequire, register } = require(`module`); +const { resolve } = require(`path`); +const { pathToFileURL } = require(`url`); const relPnpApiPath = "../../../../.pnp.cjs"; @@ -25,8 +25,8 @@ if (existsSync(absPnpApiPath)) { } const wrapWithUserWrapper = existsSync(absUserWrapperPath) - ? exports => absRequire(absUserWrapperPath)(exports) - : exports => exports; + ? (exports) => absRequire(absUserWrapperPath)(exports) + : (exports) => exports; // Defer to the real typescript/lib/tsc.js your application uses module.exports = wrapWithUserWrapper(absRequire(`typescript/lib/tsc.js`)); diff --git a/.yarn/sdks/typescript/lib/tsserver.js b/.yarn/sdks/typescript/lib/tsserver.js index 6249c46..deb0374 100644 --- a/.yarn/sdks/typescript/lib/tsserver.js +++ b/.yarn/sdks/typescript/lib/tsserver.js @@ -1,9 +1,9 @@ #!/usr/bin/env node -const {existsSync} = require(`fs`); -const {createRequire, register} = require(`module`); -const {resolve} = require(`path`); -const {pathToFileURL} = require(`url`); +const { existsSync } = require(`fs`); +const { createRequire, register } = require(`module`); +const { resolve } = require(`path`); +const { pathToFileURL } = require(`url`); const relPnpApiPath = "../../../../.pnp.cjs"; @@ -25,28 +25,30 @@ if (existsSync(absPnpApiPath)) { } const wrapWithUserWrapper = existsSync(absUserWrapperPath) - ? exports => absRequire(absUserWrapperPath)(exports) - : exports => exports; + ? (exports) => absRequire(absUserWrapperPath)(exports) + : (exports) => exports; -const moduleWrapper = exports => { +const moduleWrapper = (exports) => { return wrapWithUserWrapper(moduleWrapperFn(exports)); }; -const moduleWrapperFn = tsserver => { +const moduleWrapperFn = (tsserver) => { if (!process.versions.pnp) { return tsserver; } - const {isAbsolute} = require(`path`); + const { isAbsolute } = require(`path`); const pnpApi = require(`pnpapi`); - const isVirtual = str => str.match(/\/(\$\$virtual|__virtual__)\//); - const isPortal = str => str.startsWith("portal:/"); - const normalize = str => str.replace(/\\/g, `/`).replace(/^\/?/, `/`); + const isVirtual = (str) => str.match(/\/(\$\$virtual|__virtual__)\//); + const isPortal = (str) => str.startsWith("portal:/"); + const normalize = (str) => str.replace(/\\/g, `/`).replace(/^\/?/, `/`); - const dependencyTreeRoots = new Set(pnpApi.getDependencyTreeRoots().map(locator => { - return `${locator.name}@${locator.reference}`; - })); + const dependencyTreeRoots = new Set( + pnpApi.getDependencyTreeRoots().map((locator) => { + return `${locator.name}@${locator.reference}`; + }), + ); // VSCode sends the zip paths to TS using the "zip://" prefix, that TS // doesn't understand. This layer makes sure to remove the protocol @@ -54,7 +56,11 @@ const moduleWrapperFn = tsserver => { function toEditorPath(str) { // We add the `zip:` prefix to both `.zip/` paths and virtual paths - if (isAbsolute(str) && !str.match(/^\^?(zip:|\/zip\/)/) && (str.match(/\.zip\//) || isVirtual(str))) { + if ( + isAbsolute(str) && + !str.match(/^\^?(zip:|\/zip\/)/) && + (str.match(/\.zip\//) || isVirtual(str)) + ) { // We also take the opportunity to turn virtual paths into physical ones; // this makes it much easier to work with workspaces that list peer // dependencies, since otherwise Ctrl+Click would bring us to the virtual @@ -68,7 +74,11 @@ const moduleWrapperFn = tsserver => { const resolved = isVirtual(str) ? pnpApi.resolveVirtual(str) : str; if (resolved) { const locator = pnpApi.findPackageLocator(resolved); - if (locator && (dependencyTreeRoots.has(`${locator.name}@${locator.reference}`) || isPortal(locator.reference))) { + if ( + locator && + (dependencyTreeRoots.has(`${locator.name}@${locator.reference}`) || + isPortal(locator.reference)) + ) { str = resolved; } } @@ -96,41 +106,55 @@ const moduleWrapperFn = tsserver => { // Before | ^/zip/c:/foo/bar.zip/package.json // After | ^/zip//c:/foo/bar.zip/package.json // - case `vscode <1.61`: { - str = `^zip:${str}`; - } break; + case `vscode <1.61`: + { + str = `^zip:${str}`; + } + break; - case `vscode <1.66`: { - str = `^/zip/${str}`; - } break; + case `vscode <1.66`: + { + str = `^/zip/${str}`; + } + break; - case `vscode <1.68`: { - str = `^/zip${str}`; - } break; + case `vscode <1.68`: + { + str = `^/zip${str}`; + } + break; - case `vscode`: { - str = `^/zip/${str}`; - } break; + case `vscode`: + { + str = `^/zip/${str}`; + } + break; // To make "go to definition" work, // We have to resolve the actual file system path from virtual path // and convert scheme to supported by [vim-rzip](https://github.com/lbrayner/vim-rzip) - case `coc-nvim`: { - str = normalize(resolved).replace(/\.zip\//, `.zip::`); - str = resolve(`zipfile:${str}`); - } break; + case `coc-nvim`: + { + str = normalize(resolved).replace(/\.zip\//, `.zip::`); + str = resolve(`zipfile:${str}`); + } + break; // Support neovim native LSP and [typescript-language-server](https://github.com/theia-ide/typescript-language-server) // We have to resolve the actual file system path from virtual path, // everything else is up to neovim - case `neovim`: { - str = normalize(resolved).replace(/\.zip\//, `.zip::`); - str = `zipfile://${str}`; - } break; + case `neovim`: + { + str = normalize(resolved).replace(/\.zip\//, `.zip::`); + str = `zipfile://${str}`; + } + break; - default: { - str = `zip:${str}`; - } break; + default: + { + str = `zip:${str}`; + } + break; } } else { str = str.replace(/^\/?/, process.platform === `win32` ? `` : `/`); @@ -142,26 +166,35 @@ const moduleWrapperFn = tsserver => { function fromEditorPath(str) { switch (hostInfo) { - case `coc-nvim`: { - str = str.replace(/\.zip::/, `.zip/`); - // The path for coc-nvim is in format of //zipfile://.yarn/... - // So in order to convert it back, we use .* to match all the thing - // before `zipfile:` - return process.platform === `win32` - ? str.replace(/^.*zipfile:\//, ``) - : str.replace(/^.*zipfile:/, ``); - } break; + case `coc-nvim`: + { + str = str.replace(/\.zip::/, `.zip/`); + // The path for coc-nvim is in format of //zipfile://.yarn/... + // So in order to convert it back, we use .* to match all the thing + // before `zipfile:` + return process.platform === `win32` + ? str.replace(/^.*zipfile:\//, ``) + : str.replace(/^.*zipfile:/, ``); + } + break; - case `neovim`: { - str = str.replace(/\.zip::/, `.zip/`); - // The path for neovim is in format of zipfile:////.yarn/... - return str.replace(/^zipfile:\/\//, ``); - } break; + case `neovim`: + { + str = str.replace(/\.zip::/, `.zip/`); + // The path for neovim is in format of zipfile:////.yarn/... + return str.replace(/^zipfile:\/\//, ``); + } + break; case `vscode`: - default: { - return str.replace(/^\^?(zip:|\/zip(\/ts-nul-authority)?)\/+/, process.platform === `win32` ? `` : `/`) - } break; + default: + { + return str.replace( + /^\^?(zip:|\/zip(\/ts-nul-authority)?)\/+/, + process.platform === `win32` ? `` : `/`, + ); + } + break; } } @@ -173,8 +206,9 @@ const moduleWrapperFn = tsserver => { // TypeScript already does local loads and if this code is running the user trusts the workspace // https://github.com/microsoft/vscode/issues/45856 const ConfiguredProject = tsserver.server.ConfiguredProject; - const {enablePluginsWithOptions: originalEnablePluginsWithOptions} = ConfiguredProject.prototype; - ConfiguredProject.prototype.enablePluginsWithOptions = function() { + const { enablePluginsWithOptions: originalEnablePluginsWithOptions } = + ConfiguredProject.prototype; + ConfiguredProject.prototype.enablePluginsWithOptions = function () { this.projectService.allowLocalPluginLoads = true; return originalEnablePluginsWithOptions.apply(this, arguments); }; @@ -184,12 +218,13 @@ const moduleWrapperFn = tsserver => { // like an absolute path of ours and normalize it. const Session = tsserver.server.Session; - const {onMessage: originalOnMessage, send: originalSend} = Session.prototype; + const { onMessage: originalOnMessage, send: originalSend } = + Session.prototype; let hostInfo = `unknown`; Object.assign(Session.prototype, { onMessage(/** @type {string | object} */ message) { - const isStringMessage = typeof message === 'string'; + const isStringMessage = typeof message === "string"; const parsedMessage = isStringMessage ? JSON.parse(message) : message; if ( @@ -200,10 +235,12 @@ const moduleWrapperFn = tsserver => { ) { hostInfo = parsedMessage.arguments.hostInfo; if (hostInfo === `vscode` && process.env.VSCODE_IPC_HOOK) { - const [, major, minor] = (process.env.VSCODE_IPC_HOOK.match( - // The RegExp from https://semver.org/ but without the caret at the start - /(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/ - ) ?? []).map(Number) + const [, major, minor] = ( + process.env.VSCODE_IPC_HOOK.match( + // The RegExp from https://semver.org/ but without the caret at the start + /(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/, + ) ?? [] + ).map(Number); if (major === 1) { if (minor < 61) { @@ -217,27 +254,39 @@ const moduleWrapperFn = tsserver => { } } - const processedMessageJSON = JSON.stringify(parsedMessage, (key, value) => { - return typeof value === 'string' ? fromEditorPath(value) : value; - }); + const processedMessageJSON = JSON.stringify( + parsedMessage, + (key, value) => { + return typeof value === "string" ? fromEditorPath(value) : value; + }, + ); return originalOnMessage.call( this, - isStringMessage ? processedMessageJSON : JSON.parse(processedMessageJSON) + isStringMessage + ? processedMessageJSON + : JSON.parse(processedMessageJSON), ); }, send(/** @type {any} */ msg) { - return originalSend.call(this, JSON.parse(JSON.stringify(msg, (key, value) => { - return typeof value === `string` ? toEditorPath(value) : value; - }))); - } + return originalSend.call( + this, + JSON.parse( + JSON.stringify(msg, (key, value) => { + return typeof value === `string` ? toEditorPath(value) : value; + }), + ), + ); + }, }); return tsserver; }; -const [major, minor] = absRequire(`typescript/package.json`).version.split(`.`, 2).map(value => parseInt(value, 10)); +const [major, minor] = absRequire(`typescript/package.json`) + .version.split(`.`, 2) + .map((value) => parseInt(value, 10)); // In TypeScript@>=5.5 the tsserver uses the public TypeScript API so that needs to be patched as well. // Ref https://github.com/microsoft/TypeScript/pull/55326 if (major > 5 || (major === 5 && minor >= 5)) { diff --git a/.yarn/sdks/typescript/lib/tsserverlibrary.js b/.yarn/sdks/typescript/lib/tsserverlibrary.js index 0e50e0a..492d62d 100644 --- a/.yarn/sdks/typescript/lib/tsserverlibrary.js +++ b/.yarn/sdks/typescript/lib/tsserverlibrary.js @@ -1,9 +1,9 @@ #!/usr/bin/env node -const {existsSync} = require(`fs`); -const {createRequire, register} = require(`module`); -const {resolve} = require(`path`); -const {pathToFileURL} = require(`url`); +const { existsSync } = require(`fs`); +const { createRequire, register } = require(`module`); +const { resolve } = require(`path`); +const { pathToFileURL } = require(`url`); const relPnpApiPath = "../../../../.pnp.cjs"; @@ -25,28 +25,30 @@ if (existsSync(absPnpApiPath)) { } const wrapWithUserWrapper = existsSync(absUserWrapperPath) - ? exports => absRequire(absUserWrapperPath)(exports) - : exports => exports; + ? (exports) => absRequire(absUserWrapperPath)(exports) + : (exports) => exports; -const moduleWrapper = exports => { +const moduleWrapper = (exports) => { return wrapWithUserWrapper(moduleWrapperFn(exports)); }; -const moduleWrapperFn = tsserver => { +const moduleWrapperFn = (tsserver) => { if (!process.versions.pnp) { return tsserver; } - const {isAbsolute} = require(`path`); + const { isAbsolute } = require(`path`); const pnpApi = require(`pnpapi`); - const isVirtual = str => str.match(/\/(\$\$virtual|__virtual__)\//); - const isPortal = str => str.startsWith("portal:/"); - const normalize = str => str.replace(/\\/g, `/`).replace(/^\/?/, `/`); + const isVirtual = (str) => str.match(/\/(\$\$virtual|__virtual__)\//); + const isPortal = (str) => str.startsWith("portal:/"); + const normalize = (str) => str.replace(/\\/g, `/`).replace(/^\/?/, `/`); - const dependencyTreeRoots = new Set(pnpApi.getDependencyTreeRoots().map(locator => { - return `${locator.name}@${locator.reference}`; - })); + const dependencyTreeRoots = new Set( + pnpApi.getDependencyTreeRoots().map((locator) => { + return `${locator.name}@${locator.reference}`; + }), + ); // VSCode sends the zip paths to TS using the "zip://" prefix, that TS // doesn't understand. This layer makes sure to remove the protocol @@ -54,7 +56,11 @@ const moduleWrapperFn = tsserver => { function toEditorPath(str) { // We add the `zip:` prefix to both `.zip/` paths and virtual paths - if (isAbsolute(str) && !str.match(/^\^?(zip:|\/zip\/)/) && (str.match(/\.zip\//) || isVirtual(str))) { + if ( + isAbsolute(str) && + !str.match(/^\^?(zip:|\/zip\/)/) && + (str.match(/\.zip\//) || isVirtual(str)) + ) { // We also take the opportunity to turn virtual paths into physical ones; // this makes it much easier to work with workspaces that list peer // dependencies, since otherwise Ctrl+Click would bring us to the virtual @@ -68,7 +74,11 @@ const moduleWrapperFn = tsserver => { const resolved = isVirtual(str) ? pnpApi.resolveVirtual(str) : str; if (resolved) { const locator = pnpApi.findPackageLocator(resolved); - if (locator && (dependencyTreeRoots.has(`${locator.name}@${locator.reference}`) || isPortal(locator.reference))) { + if ( + locator && + (dependencyTreeRoots.has(`${locator.name}@${locator.reference}`) || + isPortal(locator.reference)) + ) { str = resolved; } } @@ -96,41 +106,55 @@ const moduleWrapperFn = tsserver => { // Before | ^/zip/c:/foo/bar.zip/package.json // After | ^/zip//c:/foo/bar.zip/package.json // - case `vscode <1.61`: { - str = `^zip:${str}`; - } break; + case `vscode <1.61`: + { + str = `^zip:${str}`; + } + break; - case `vscode <1.66`: { - str = `^/zip/${str}`; - } break; + case `vscode <1.66`: + { + str = `^/zip/${str}`; + } + break; - case `vscode <1.68`: { - str = `^/zip${str}`; - } break; + case `vscode <1.68`: + { + str = `^/zip${str}`; + } + break; - case `vscode`: { - str = `^/zip/${str}`; - } break; + case `vscode`: + { + str = `^/zip/${str}`; + } + break; // To make "go to definition" work, // We have to resolve the actual file system path from virtual path // and convert scheme to supported by [vim-rzip](https://github.com/lbrayner/vim-rzip) - case `coc-nvim`: { - str = normalize(resolved).replace(/\.zip\//, `.zip::`); - str = resolve(`zipfile:${str}`); - } break; + case `coc-nvim`: + { + str = normalize(resolved).replace(/\.zip\//, `.zip::`); + str = resolve(`zipfile:${str}`); + } + break; // Support neovim native LSP and [typescript-language-server](https://github.com/theia-ide/typescript-language-server) // We have to resolve the actual file system path from virtual path, // everything else is up to neovim - case `neovim`: { - str = normalize(resolved).replace(/\.zip\//, `.zip::`); - str = `zipfile://${str}`; - } break; + case `neovim`: + { + str = normalize(resolved).replace(/\.zip\//, `.zip::`); + str = `zipfile://${str}`; + } + break; - default: { - str = `zip:${str}`; - } break; + default: + { + str = `zip:${str}`; + } + break; } } else { str = str.replace(/^\/?/, process.platform === `win32` ? `` : `/`); @@ -142,26 +166,35 @@ const moduleWrapperFn = tsserver => { function fromEditorPath(str) { switch (hostInfo) { - case `coc-nvim`: { - str = str.replace(/\.zip::/, `.zip/`); - // The path for coc-nvim is in format of //zipfile://.yarn/... - // So in order to convert it back, we use .* to match all the thing - // before `zipfile:` - return process.platform === `win32` - ? str.replace(/^.*zipfile:\//, ``) - : str.replace(/^.*zipfile:/, ``); - } break; + case `coc-nvim`: + { + str = str.replace(/\.zip::/, `.zip/`); + // The path for coc-nvim is in format of //zipfile://.yarn/... + // So in order to convert it back, we use .* to match all the thing + // before `zipfile:` + return process.platform === `win32` + ? str.replace(/^.*zipfile:\//, ``) + : str.replace(/^.*zipfile:/, ``); + } + break; - case `neovim`: { - str = str.replace(/\.zip::/, `.zip/`); - // The path for neovim is in format of zipfile:////.yarn/... - return str.replace(/^zipfile:\/\//, ``); - } break; + case `neovim`: + { + str = str.replace(/\.zip::/, `.zip/`); + // The path for neovim is in format of zipfile:////.yarn/... + return str.replace(/^zipfile:\/\//, ``); + } + break; case `vscode`: - default: { - return str.replace(/^\^?(zip:|\/zip(\/ts-nul-authority)?)\/+/, process.platform === `win32` ? `` : `/`) - } break; + default: + { + return str.replace( + /^\^?(zip:|\/zip(\/ts-nul-authority)?)\/+/, + process.platform === `win32` ? `` : `/`, + ); + } + break; } } @@ -173,8 +206,9 @@ const moduleWrapperFn = tsserver => { // TypeScript already does local loads and if this code is running the user trusts the workspace // https://github.com/microsoft/vscode/issues/45856 const ConfiguredProject = tsserver.server.ConfiguredProject; - const {enablePluginsWithOptions: originalEnablePluginsWithOptions} = ConfiguredProject.prototype; - ConfiguredProject.prototype.enablePluginsWithOptions = function() { + const { enablePluginsWithOptions: originalEnablePluginsWithOptions } = + ConfiguredProject.prototype; + ConfiguredProject.prototype.enablePluginsWithOptions = function () { this.projectService.allowLocalPluginLoads = true; return originalEnablePluginsWithOptions.apply(this, arguments); }; @@ -184,12 +218,13 @@ const moduleWrapperFn = tsserver => { // like an absolute path of ours and normalize it. const Session = tsserver.server.Session; - const {onMessage: originalOnMessage, send: originalSend} = Session.prototype; + const { onMessage: originalOnMessage, send: originalSend } = + Session.prototype; let hostInfo = `unknown`; Object.assign(Session.prototype, { onMessage(/** @type {string | object} */ message) { - const isStringMessage = typeof message === 'string'; + const isStringMessage = typeof message === "string"; const parsedMessage = isStringMessage ? JSON.parse(message) : message; if ( @@ -200,10 +235,12 @@ const moduleWrapperFn = tsserver => { ) { hostInfo = parsedMessage.arguments.hostInfo; if (hostInfo === `vscode` && process.env.VSCODE_IPC_HOOK) { - const [, major, minor] = (process.env.VSCODE_IPC_HOOK.match( - // The RegExp from https://semver.org/ but without the caret at the start - /(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/ - ) ?? []).map(Number) + const [, major, minor] = ( + process.env.VSCODE_IPC_HOOK.match( + // The RegExp from https://semver.org/ but without the caret at the start + /(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/, + ) ?? [] + ).map(Number); if (major === 1) { if (minor < 61) { @@ -217,27 +254,39 @@ const moduleWrapperFn = tsserver => { } } - const processedMessageJSON = JSON.stringify(parsedMessage, (key, value) => { - return typeof value === 'string' ? fromEditorPath(value) : value; - }); + const processedMessageJSON = JSON.stringify( + parsedMessage, + (key, value) => { + return typeof value === "string" ? fromEditorPath(value) : value; + }, + ); return originalOnMessage.call( this, - isStringMessage ? processedMessageJSON : JSON.parse(processedMessageJSON) + isStringMessage + ? processedMessageJSON + : JSON.parse(processedMessageJSON), ); }, send(/** @type {any} */ msg) { - return originalSend.call(this, JSON.parse(JSON.stringify(msg, (key, value) => { - return typeof value === `string` ? toEditorPath(value) : value; - }))); - } + return originalSend.call( + this, + JSON.parse( + JSON.stringify(msg, (key, value) => { + return typeof value === `string` ? toEditorPath(value) : value; + }), + ), + ); + }, }); return tsserver; }; -const [major, minor] = absRequire(`typescript/package.json`).version.split(`.`, 2).map(value => parseInt(value, 10)); +const [major, minor] = absRequire(`typescript/package.json`) + .version.split(`.`, 2) + .map((value) => parseInt(value, 10)); // In TypeScript@>=5.5 the tsserver uses the public TypeScript API so that needs to be patched as well. // Ref https://github.com/microsoft/TypeScript/pull/55326 if (major > 5 || (major === 5 && minor >= 5)) { diff --git a/.yarn/sdks/typescript/lib/typescript.js b/.yarn/sdks/typescript/lib/typescript.js index 7b6cc22..b862c45 100644 --- a/.yarn/sdks/typescript/lib/typescript.js +++ b/.yarn/sdks/typescript/lib/typescript.js @@ -1,9 +1,9 @@ #!/usr/bin/env node -const {existsSync} = require(`fs`); -const {createRequire, register} = require(`module`); -const {resolve} = require(`path`); -const {pathToFileURL} = require(`url`); +const { existsSync } = require(`fs`); +const { createRequire, register } = require(`module`); +const { resolve } = require(`path`); +const { pathToFileURL } = require(`url`); const relPnpApiPath = "../../../../.pnp.cjs"; @@ -25,8 +25,8 @@ if (existsSync(absPnpApiPath)) { } const wrapWithUserWrapper = existsSync(absUserWrapperPath) - ? exports => absRequire(absUserWrapperPath)(exports) - : exports => exports; + ? (exports) => absRequire(absUserWrapperPath)(exports) + : (exports) => exports; // Defer to the real typescript your application uses module.exports = wrapWithUserWrapper(absRequire(`typescript`)); diff --git a/README.md b/README.md index 74872fd..780c92d 100644 --- a/README.md +++ b/README.md @@ -18,11 +18,11 @@ export default tseslint.config({ languageOptions: { // other options... parserOptions: { - project: ['./tsconfig.node.json', './tsconfig.app.json'], + project: ["./tsconfig.node.json", "./tsconfig.app.json"], tsconfigRootDir: import.meta.dirname, }, }, -}) +}); ``` - Replace `tseslint.configs.recommended` to `tseslint.configs.recommendedTypeChecked` or `tseslint.configs.strictTypeChecked` @@ -31,11 +31,11 @@ export default tseslint.config({ ```js // eslint.config.js -import react from 'eslint-plugin-react' +import react from "eslint-plugin-react"; export default tseslint.config({ // Set the react version - settings: { react: { version: '18.3' } }, + settings: { react: { version: "18.3" } }, plugins: { // Add the react plugin react, @@ -44,7 +44,7 @@ export default tseslint.config({ // other rules... // Enable its recommended rules ...react.configs.recommended.rules, - ...react.configs['jsx-runtime'].rules, + ...react.configs["jsx-runtime"].rules, }, -}) +}); ``` diff --git a/eslint.config.js b/eslint.config.js index c4bc8f3..4d99da8 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -1,26 +1,26 @@ -import js from '@eslint/js' -import globals from 'globals' -import reactHooks from 'eslint-plugin-react-hooks' -import reactRefresh from 'eslint-plugin-react-refresh' -import tseslint from 'typescript-eslint' +import js from "@eslint/js"; +import globals from "globals"; +import reactHooks from "eslint-plugin-react-hooks"; +import reactRefresh from "eslint-plugin-react-refresh"; +import tseslint from "typescript-eslint"; export default tseslint.config({ extends: [js.configs.recommended, ...tseslint.configs.recommended], - files: ['**/*.{ts,tsx}'], - ignores: ['dist'], + files: ["**/*.{ts,tsx}"], + ignores: ["dist"], languageOptions: { ecmaVersion: 2020, globals: globals.browser, }, plugins: { - 'react-hooks': reactHooks, - 'react-refresh': reactRefresh, + "react-hooks": reactHooks, + "react-refresh": reactRefresh, }, rules: { ...reactHooks.configs.recommended.rules, - 'react-refresh/only-export-components': [ - 'warn', + "react-refresh/only-export-components": [ + "warn", { allowConstantExport: true }, ], }, -}) +}); diff --git a/index.html b/index.html index 11ae7e9..276b23e 100644 --- a/index.html +++ b/index.html @@ -1,31 +1,32 @@ + + + + + + + + + + + + + LNVPS + + + + - - - - - - - - - - - - - LNVPS - - - - - - -
- - - - - \ No newline at end of file + +
+ + + + diff --git a/package.json b/package.json index 93032e2..2809d8a 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "eslint-plugin-react-refresh": "^0.4.9", "globals": "^15.9.0", "postcss": "^8.4.41", + "prettier": "^3.3.3", "tailwindcss": "^3.4.8", "typescript": "^5.5.3", "typescript-eslint": "^8.0.0", diff --git a/postcss.config.js b/postcss.config.js index 2e7af2b..2aa7205 100644 --- a/postcss.config.js +++ b/postcss.config.js @@ -3,4 +3,4 @@ export default { tailwindcss: {}, autoprefixer: {}, }, -} +}; diff --git a/src/App.tsx b/src/App.tsx index 69641fe..6828019 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,9 +1,12 @@ -import { SnortContext } from "@snort/system-react" -import { CostInterval, DiskType, MachineSpec } from "./api" -import VpsCard from "./components/vps-card" -import { GiB, NostrProfile } from "./const" -import { NostrSystem } from "@snort/system" -import Profile from "./components/profile" +import { SnortContext } from "@snort/system-react"; +import { CostInterval, DiskType, MachineSpec } from "./api"; +import VpsCard from "./components/vps-card"; +import { GiB, NostrProfile } from "./const"; +import { NostrSystem } from "@snort/system"; +import Profile from "./components/profile"; +import { AsyncButton } from "./components/button"; +import { loginNip7 } from "./login"; +import LoginButton from "./components/login-button"; const Offers: Array = [ { @@ -13,13 +16,13 @@ const Offers: Array = [ ram: 2 * GiB, disk: { type: DiskType.SSD, - size: 80 * GiB + size: 80 * GiB, }, cost: { interval: CostInterval.Month, count: 3, currency: "EUR", - } + }, }, { id: "4x4x160", @@ -28,13 +31,13 @@ const Offers: Array = [ ram: 4 * GiB, disk: { type: DiskType.SSD, - size: 160 * GiB + size: 160 * GiB, }, cost: { interval: CostInterval.Month, count: 5, currency: "EUR", - } + }, }, { id: "8x8x400", @@ -43,50 +46,58 @@ const Offers: Array = [ ram: 8 * GiB, disk: { type: DiskType.SSD, - size: 400 * GiB + size: 400 * GiB, }, cost: { interval: CostInterval.Month, count: 12, currency: "EUR", - } - } -] + }, + }, +]; const system = new NostrSystem({}); [ "wss://relay.snort.social/", "wss://relay.damus.io/", "wss://relay.nostr.band/", - "wss://nos.lol/" -].forEach(a => system.ConnectToRelay(a, { read: true, write: true })); + "wss://nos.lol/", +].forEach((a) => system.ConnectToRelay(a, { read: true, write: true })); export default function App() { - return (
-

LNVPS

+
+ LNVPS + +
-

VPS

+

VPS Offers

- {Offers.map(a => )} + {Offers.map((a) => ( + + ))}
- Please email sales after paying the invoice with your order id, desired OS and ssh key. + + Please email sales after + paying the invoice with your order id, desired OS and ssh key. + You can also find us on nostr:
{NostrProfile.encode()}
- All VPS come with 1x IPv4 and 1x IPv6 address and unmetered traffic. + All VPS come with 1x IPv4 and 1x IPv6 address and unmetered + traffic.
- ) + ); } diff --git a/src/components/button.tsx b/src/components/button.tsx new file mode 100644 index 0000000..9662b3b --- /dev/null +++ b/src/components/button.tsx @@ -0,0 +1,21 @@ +import { forwardRef, HTMLProps } from "react"; + +export type AsyncButtonProps = { + onClick?: (e: React.MouseEvent) => Promise; +} & Omit, "type" | "ref" | "onClick">; + +const AsyncButton = forwardRef( + function AsyncButton(props, ref) { + return ( + + ); + }, +); + +export { AsyncButton }; diff --git a/src/components/bytes.tsx b/src/components/bytes.tsx index 0d91850..e7c1035 100644 --- a/src/components/bytes.tsx +++ b/src/components/bytes.tsx @@ -1,19 +1,19 @@ -import { GiB, KiB, MiB, TiB } from "../const" +import { GiB, KiB, MiB, TiB } from "../const"; interface BytesSizeProps { - value: number, - precision?: number + value: number; + precision?: number; } export default function BytesSize(props: BytesSizeProps) { - if (props.value >= TiB) { - return (props.value / TiB).toFixed(props.precision ?? 0) + "TB"; - } else if (props.value >= GiB) { - return (props.value / GiB).toFixed(props.precision ?? 0) + "GB"; - } else if (props.value >= MiB) { - return (props.value / MiB).toFixed(props.precision ?? 0) + "MB"; - } else if (props.value >= KiB) { - return (props.value / KiB).toFixed(props.precision ?? 0) + "KB"; - } else { - return (props.value).toFixed(props.precision ?? 0) + "B"; - } -} \ No newline at end of file + if (props.value >= TiB) { + return (props.value / TiB).toFixed(props.precision ?? 0) + "TB"; + } else if (props.value >= GiB) { + return (props.value / GiB).toFixed(props.precision ?? 0) + "GB"; + } else if (props.value >= MiB) { + return (props.value / MiB).toFixed(props.precision ?? 0) + "MB"; + } else if (props.value >= KiB) { + return (props.value / KiB).toFixed(props.precision ?? 0) + "KB"; + } else { + return props.value.toFixed(props.precision ?? 0) + "B"; + } +} diff --git a/src/components/cost.tsx b/src/components/cost.tsx index e356d66..ccbba58 100644 --- a/src/components/cost.tsx +++ b/src/components/cost.tsx @@ -1,14 +1,22 @@ import { CostInterval, MachineSpec } from "../api"; export default function CostLabel({ cost }: { cost: MachineSpec["cost"] }) { - function intervalName(n: number) { - switch (n) { - case CostInterval.Hour: return "Hour" - case CostInterval.Day: return "Day" - case CostInterval.Month: return "Month" - case CostInterval.Year: return "Year" - } + function intervalName(n: number) { + switch (n) { + case CostInterval.Hour: + return "Hour"; + case CostInterval.Day: + return "Day"; + case CostInterval.Month: + return "Month"; + case CostInterval.Year: + return "Year"; } + } - return <>{cost.count} {cost.currency}/{intervalName(cost.interval)} -} \ No newline at end of file + return ( + <> + {cost.count} {cost.currency}/{intervalName(cost.interval)} + + ); +} diff --git a/src/components/login-button.tsx b/src/components/login-button.tsx new file mode 100644 index 0000000..e68f450 --- /dev/null +++ b/src/components/login-button.tsx @@ -0,0 +1,24 @@ +import { SnortContext } from "@snort/system-react"; +import { useContext } from "react"; +import { AsyncButton } from "./button"; +import { loginNip7 } from "../login"; +import useLogin from "../hooks/login"; +import Profile from "./profile"; +import { NostrLink } from "@snort/system"; + +export default function LoginButton() { + const system = useContext(SnortContext); + const login = useLogin(); + + return !login ? ( + { + await loginNip7(system); + }} + > + Sign In + + ) : ( + + ); +} diff --git a/src/components/pay-button.tsx b/src/components/pay-button.tsx index a2669da..7ab836c 100644 --- a/src/components/pay-button.tsx +++ b/src/components/pay-button.tsx @@ -1,44 +1,64 @@ import { MachineSpec } from "../api"; -import "./pay-button.css" +import "./pay-button.css"; declare global { - interface Window { - btcpay?: { - appendInvoiceFrame(invoiceId: string): void; - } - } + interface Window { + btcpay?: { + appendInvoiceFrame(invoiceId: string): void; + }; + } } export default function VpsPayButton({ spec }: { spec: MachineSpec }) { - const serverUrl = "https://btcpay.v0l.io/api/v1/invoices"; + const serverUrl = "https://btcpay.v0l.io/api/v1/invoices"; - function handleFormSubmit(event: React.FormEvent) { - event.preventDefault(); - const form = event.target as HTMLFormElement; - const xhttp = new XMLHttpRequest(); - xhttp.onreadystatechange = function () { - if (this.readyState == 4 && this.status == 200 && this.responseText) { - window.btcpay?.appendInvoiceFrame(JSON.parse(this.responseText).invoiceId); - } - }; - xhttp.open('POST', serverUrl, true); - xhttp.send(new FormData(form)); - } + function handleFormSubmit(event: React.FormEvent) { + event.preventDefault(); + const form = event.target as HTMLFormElement; + const xhttp = new XMLHttpRequest(); + xhttp.onreadystatechange = function () { + if (this.readyState == 4 && this.status == 200 && this.responseText) { + window.btcpay?.appendInvoiceFrame( + JSON.parse(this.responseText).invoiceId, + ); + } + }; + xhttp.open("POST", serverUrl, true); + xhttp.send(new FormData(form)); + } - if (!spec.active) { - return
- Unavailable -
- } + if (!spec.active) { + return ( +
+ Unavailable +
+ ); + } - return
- - - - - - + return ( + + + + + + +
-} \ No newline at end of file + ); +} diff --git a/src/components/profile.tsx b/src/components/profile.tsx index 422773d..9a01c52 100644 --- a/src/components/profile.tsx +++ b/src/components/profile.tsx @@ -3,11 +3,18 @@ import { NostrLink } from "@snort/system"; import { useUserProfile } from "@snort/system-react"; export default function Profile({ link }: { link: NostrLink }) { - const profile = useUserProfile(link.id); - return
- -
- {profile?.display_name ?? profile?.name ?? hexToBech32("npub", link.id).slice(0, 12)} -
+ const profile = useUserProfile(link.id); + return ( +
+ +
+ {profile?.display_name ?? + profile?.name ?? + hexToBech32("npub", link.id).slice(0, 12)} +
-} \ No newline at end of file + ); +} diff --git a/src/components/vps-card.tsx b/src/components/vps-card.tsx index 7c8026a..890caaf 100644 --- a/src/components/vps-card.tsx +++ b/src/components/vps-card.tsx @@ -4,14 +4,23 @@ import CostLabel from "./cost"; import VpsPayButton from "./pay-button"; export default function VpsCard({ spec }: { spec: MachineSpec }) { - return
-

{spec.id}

-
    -
  • CPU: {spec.cpu}vCPU
  • -
  • RAM:
  • -
  • {spec.disk.type === DiskType.SSD ? "SSD" : "HDD"}:
  • -
-

- + return ( +
+

{spec.id}

+
    +
  • CPU: {spec.cpu}vCPU
  • +
  • + RAM: +
  • +
  • + {spec.disk.type === DiskType.SSD ? "SSD" : "HDD"}:{" "} + +
  • +
+

+ +

+
-} \ No newline at end of file + ); +} diff --git a/src/const.ts b/src/const.ts index 4e88358..bf57b14 100644 --- a/src/const.ts +++ b/src/const.ts @@ -12,9 +12,15 @@ export const GB = KB * 1000; export const TB = GB * 1000; export const PB = TB * 1000; -export const NostrProfile = new NostrLink(NostrPrefix.Profile, - "fcd818454002a6c47a980393f0549ac6e629d28d5688114bb60d831b5c1832a7", - undefined, undefined, [ - "wss://nos.lol/", "wss://relay.nostr.bg/", "wss://relay.damus.io", "wss://relay.snort.social/" -] +export const NostrProfile = new NostrLink( + NostrPrefix.Profile, + "fcd818454002a6c47a980393f0549ac6e629d28d5688114bb60d831b5c1832a7", + undefined, + undefined, + [ + "wss://nos.lol/", + "wss://relay.nostr.bg/", + "wss://relay.damus.io", + "wss://relay.snort.social/", + ], ); diff --git a/src/hooks/login.tsx b/src/hooks/login.tsx new file mode 100644 index 0000000..930cd4e --- /dev/null +++ b/src/hooks/login.tsx @@ -0,0 +1,12 @@ +import { useSyncExternalStore } from "react"; +import { Login } from "../login"; + +export default function useLogin() { + return useSyncExternalStore( + (c) => { + Login?.on("change", c); + return () => Login?.off("change", c); + }, + () => Login, + ); +} diff --git a/src/index.css b/src/index.css index 71efdb6..ceb7287 100644 --- a/src/index.css +++ b/src/index.css @@ -35,4 +35,4 @@ h3 { a { text-decoration: underline; -} \ No newline at end of file +} diff --git a/src/login.ts b/src/login.ts new file mode 100644 index 0000000..f697f7c --- /dev/null +++ b/src/login.ts @@ -0,0 +1,14 @@ +import { Nip7Signer, SystemInterface, UserState } from "@snort/system"; + +export let Login: UserState | undefined; + +export async function loginNip7(system: SystemInterface) { + const signer = new Nip7Signer(); + const pubkey = await signer.getPubKey(); + if (pubkey) { + Login = new UserState(pubkey); + await Login.init(signer, system); + } else { + throw new Error("No nostr extension found"); + } +} diff --git a/src/main.tsx b/src/main.tsx index 6f4ac9b..ef474bf 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,10 +1,10 @@ -import { StrictMode } from 'react' -import { createRoot } from 'react-dom/client' -import App from './App.tsx' -import './index.css' +import { StrictMode } from "react"; +import { createRoot } from "react-dom/client"; +import App from "./App.tsx"; +import "./index.css"; -createRoot(document.getElementById('root')!).render( +createRoot(document.getElementById("root")!).render( , -) +); diff --git a/tailwind.config.js b/tailwind.config.js index d37737f..614c86b 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -1,12 +1,8 @@ /** @type {import('tailwindcss').Config} */ export default { - content: [ - "./index.html", - "./src/**/*.{js,ts,jsx,tsx}", - ], + content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"], theme: { extend: {}, }, plugins: [], -} - +}; diff --git a/vite.config.ts b/vite.config.ts index 5a33944..9cc50ea 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,7 +1,7 @@ -import { defineConfig } from 'vite' -import react from '@vitejs/plugin-react' +import { defineConfig } from "vite"; +import react from "@vitejs/plugin-react"; // https://vitejs.dev/config/ export default defineConfig({ plugins: [react()], -}) +}); diff --git a/yarn.lock b/yarn.lock index 360602d..cbbe63b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2548,6 +2548,7 @@ __metadata: eslint-plugin-react-refresh: "npm:^0.4.9" globals: "npm:^15.9.0" postcss: "npm:^8.4.41" + prettier: "npm:^3.3.3" react: "npm:^18.3.1" react-dom: "npm:^18.3.1" tailwindcss: "npm:^3.4.8" @@ -3146,6 +3147,15 @@ __metadata: languageName: node linkType: hard +"prettier@npm:^3.3.3": + version: 3.3.3 + resolution: "prettier@npm:3.3.3" + bin: + prettier: bin/prettier.cjs + checksum: 10c0/b85828b08e7505716324e4245549b9205c0cacb25342a030ba8885aba2039a115dbcf75a0b7ca3b37bc9d101ee61fab8113fc69ca3359f2a226f1ecc07ad2e26 + languageName: node + linkType: hard + "proc-log@npm:^4.1.0, proc-log@npm:^4.2.0": version: 4.2.0 resolution: "proc-log@npm:4.2.0"