mirror of
https://github.com/EthanShoeDev/fressh.git
synced 2026-01-10 05:42:50 +00:00
more working
This commit is contained in:
@@ -9,7 +9,8 @@
|
||||
"**/android/**",
|
||||
"**/generated/**",
|
||||
"**/rust/target/**",
|
||||
"**/mobile/ios/**"
|
||||
"**/mobile/ios/**",
|
||||
"**/eslint.config.js"
|
||||
],
|
||||
"threshold": 0,
|
||||
"minTokens": 50,
|
||||
|
||||
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@@ -28,7 +28,8 @@
|
||||
"**/mnt/**",
|
||||
"**/dist/**",
|
||||
"**/node_modules/**",
|
||||
"**/android/**"
|
||||
"**/android/**",
|
||||
"**/eslint.config.js"
|
||||
],
|
||||
"[astro]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
|
||||
@@ -37,12 +37,11 @@ export default defineConfig([
|
||||
// Expo (strip conflicting plugins defined elsewhere)
|
||||
...expoConfig.map((c) => stripPlugins(c, ['@typescript-eslint'])),
|
||||
// Epic (strip conflicting plugins defined elsewhere)
|
||||
...epicConfig.map((c) => stripPlugins(c, ['import', '@typescript-eslint'])),
|
||||
...epicConfig.map((c) => stripPlugins(c, ['import'])),
|
||||
|
||||
// ts-eslint
|
||||
eslint.configs.recommended,
|
||||
...tseslint.configs.strictTypeChecked,
|
||||
...tseslint.configs.stylisticTypeChecked,
|
||||
|
||||
{
|
||||
languageOptions: {
|
||||
parserOptions: {
|
||||
|
||||
@@ -72,6 +72,15 @@ function ShellDetail() {
|
||||
const connection = sess?.connection;
|
||||
const shell = sess?.shell;
|
||||
|
||||
// If the shell disconnects, leave this screen to the list view
|
||||
useEffect(() => {
|
||||
if (!sess) return;
|
||||
if (sess.status === 'disconnected') {
|
||||
// Replace so the detail screen isn't on the stack anymore
|
||||
router.replace('/shell');
|
||||
}
|
||||
}, [router, sess]);
|
||||
|
||||
// SSH -> xterm: on initialized, replay ring head then attach live listener
|
||||
useEffect(() => {
|
||||
const xterm = xtermRef.current;
|
||||
@@ -153,13 +162,17 @@ function ShellDetail() {
|
||||
textZoom={100}
|
||||
allowsLinkPreview={false}
|
||||
textInteractionEnabled={false}
|
||||
// xterm-ish props (applied via setOptions inside the page)
|
||||
fontFamily="Menlo, ui-monospace, monospace"
|
||||
fontSize={18} // bump if it still feels small
|
||||
cursorBlink
|
||||
scrollback={10000}
|
||||
themeBackground={theme.colors.background}
|
||||
themeForeground={theme.colors.textPrimary}
|
||||
// xterm options
|
||||
options={{
|
||||
fontFamily: 'Menlo, ui-monospace, monospace',
|
||||
fontSize: 18,
|
||||
cursorBlink: true,
|
||||
scrollback: 10000,
|
||||
theme: {
|
||||
background: theme.colors.background,
|
||||
foreground: theme.colors.textPrimary,
|
||||
},
|
||||
}}
|
||||
onRenderProcessGone={() => {
|
||||
console.log('WebView render process gone -> clear()');
|
||||
const xr = xtermRef.current;
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
createFormHookContexts,
|
||||
useStore,
|
||||
} from '@tanstack/react-form';
|
||||
import React from 'react';
|
||||
import {
|
||||
Pressable,
|
||||
Switch,
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
import js from '@eslint/js';
|
||||
import { config as epicConfig } from '@epic-web/config/eslint';
|
||||
import eslint from '@eslint/js';
|
||||
import comments from '@eslint-community/eslint-plugin-eslint-comments/configs';
|
||||
import react from '@eslint-react/eslint-plugin';
|
||||
import * as tsParser from '@typescript-eslint/parser';
|
||||
import { defineConfig } from 'eslint/config';
|
||||
import eslintReact from 'eslint-plugin-react';
|
||||
import pluginReactCompiler from 'eslint-plugin-react-compiler';
|
||||
import hooksPlugin from 'eslint-plugin-react-hooks';
|
||||
import globals from 'globals';
|
||||
import reactHooks from 'eslint-plugin-react-hooks';
|
||||
import reactRefresh from 'eslint-plugin-react-refresh';
|
||||
import tseslint from 'typescript-eslint';
|
||||
import { globalIgnores, defineConfig } from 'eslint/config';
|
||||
|
||||
export default defineConfig([
|
||||
globalIgnores(['dist']),
|
||||
...epicConfig,
|
||||
|
||||
// ts-eslint
|
||||
eslint.configs.recommended,
|
||||
{
|
||||
files: ['**/*.{ts,tsx}'],
|
||||
extends: [
|
||||
js.configs.recommended,
|
||||
tseslint.configs.recommended,
|
||||
reactHooks.configs['recommended-latest'],
|
||||
reactRefresh.configs.vite,
|
||||
],
|
||||
languageOptions: {
|
||||
ecmaVersion: 2020,
|
||||
globals: globals.browser,
|
||||
@@ -24,4 +24,63 @@ export default defineConfig([
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// @eslint-react/eslint-plugin (smaller version of eslint-plugin-react)
|
||||
{
|
||||
files: ['**/*.{ts,tsx}'],
|
||||
...react.configs['recommended-type-checked'],
|
||||
languageOptions: {
|
||||
parser: tsParser,
|
||||
},
|
||||
},
|
||||
|
||||
// Lint eslint disable comments
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access -- no types
|
||||
comments.recommended,
|
||||
|
||||
// eslint-plugin-react
|
||||
// Terrible flat config support
|
||||
{
|
||||
...eslintReact.configs.flat.recommended,
|
||||
files: ['**/*.{ts,tsx}'],
|
||||
settings: { react: { version: 'detect' } },
|
||||
languageOptions: {
|
||||
...eslintReact.configs.flat.recommended?.languageOptions,
|
||||
globals: {
|
||||
...globals.serviceworker,
|
||||
...globals.browser,
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
...eslintReact.configs.flat.recommended?.plugins,
|
||||
'react-hooks': hooksPlugin,
|
||||
'react-compiler': pluginReactCompiler,
|
||||
},
|
||||
rules: {
|
||||
...hooksPlugin.configs.recommended.rules,
|
||||
'react/display-name': 'off',
|
||||
'react/prop-types': 'off',
|
||||
'react/jsx-uses-react': 'off',
|
||||
'react/react-in-jsx-scope': 'off',
|
||||
'react-compiler/react-compiler': 'error',
|
||||
},
|
||||
},
|
||||
|
||||
// Custom
|
||||
{
|
||||
ignores: [
|
||||
'dist',
|
||||
'**/*.d.ts',
|
||||
'**/.expo/**',
|
||||
'prettier.config.mjs',
|
||||
'eslint.config.js',
|
||||
],
|
||||
},
|
||||
{
|
||||
rules: {
|
||||
'@typescript-eslint/no-explicit-any': 'error',
|
||||
'@typescript-eslint/restrict-template-expressions': 'off',
|
||||
'@typescript-eslint/consistent-type-imports': 'off', // we need this to avoid including xtermjs in the RN bundle
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
".": "./dist/index.js"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"dev": "vite --config vite.config.internal.ts",
|
||||
"build:main": "tsc -b && vite build",
|
||||
"build:internal": "tsc -b && vite build --config vite.config.internal.ts",
|
||||
"fmt:check": "cross-env SORT_IMPORTS=true prettier --check .",
|
||||
@@ -35,9 +35,13 @@
|
||||
"@xterm/xterm": "^5.5.0",
|
||||
"js-base64": "^3.7.8",
|
||||
"eslint": "^9.35.0",
|
||||
"@eslint-community/eslint-plugin-eslint-comments": "^4.5.0",
|
||||
"eslint-plugin-react-hooks": "^5.2.0",
|
||||
"@eslint-react/eslint-plugin": "^1.53.0",
|
||||
"vite-plugin-singlefile": "^2.3.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.20",
|
||||
"@typescript-eslint/parser": "^8.44.0",
|
||||
"@typescript-eslint/utils": "^8.43.0",
|
||||
"globals": "^16.4.0",
|
||||
"prettier": "^3.6.2",
|
||||
"prettier-plugin-organize-imports": "^4.2.0",
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import { Terminal } from '@xterm/xterm';
|
||||
import { FitAddon } from '@xterm/addon-fit';
|
||||
import { Terminal, type ITerminalOptions, type ITheme } from '@xterm/xterm';
|
||||
import { Base64 } from 'js-base64';
|
||||
import '@xterm/xterm/css/xterm.css';
|
||||
import {
|
||||
type BridgeInboundMessage,
|
||||
type BridgeOutboundMessage,
|
||||
} from '../src/bridge';
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
@@ -17,7 +21,7 @@ declare global {
|
||||
/**
|
||||
* Post typed messages to React Native
|
||||
*/
|
||||
const post = (msg: unknown) =>
|
||||
const post = (msg: BridgeInboundMessage) =>
|
||||
window.ReactNativeWebView?.postMessage?.(JSON.stringify(msg));
|
||||
|
||||
/**
|
||||
@@ -71,32 +75,17 @@ if (window.__FRESSH_XTERM_BRIDGE__) {
|
||||
// RN -> WebView handler (write, resize, setFont, setTheme, setOptions, clear, focus)
|
||||
const handler = (e: MessageEvent<string>) => {
|
||||
try {
|
||||
const msg = JSON.parse(e.data) as
|
||||
| { type: 'write'; b64?: string; chunks?: string[] }
|
||||
| { type: 'resize'; cols?: number; rows?: number }
|
||||
| { type: 'setFont'; family?: string; size?: number }
|
||||
| { type: 'setTheme'; background?: string; foreground?: string }
|
||||
| {
|
||||
type: 'setOptions';
|
||||
opts: Partial<{
|
||||
cursorBlink: boolean;
|
||||
scrollback: number;
|
||||
fontFamily: string;
|
||||
fontSize: number;
|
||||
}>;
|
||||
}
|
||||
| { type: 'clear' }
|
||||
| { type: 'focus' };
|
||||
const msg = JSON.parse(e.data) as BridgeOutboundMessage;
|
||||
|
||||
if (!msg || typeof msg.type !== 'string') return;
|
||||
|
||||
switch (msg.type) {
|
||||
case 'write': {
|
||||
if (typeof msg.b64 === 'string') {
|
||||
if ('b64' in msg) {
|
||||
const bytes = Base64.toUint8Array(msg.b64);
|
||||
term.write(bytes);
|
||||
post({ type: 'debug', message: `write(bytes=${bytes.length})` });
|
||||
} else if (Array.isArray(msg.chunks)) {
|
||||
} else if ('chunks' in msg && Array.isArray(msg.chunks)) {
|
||||
for (const b64 of msg.chunks) {
|
||||
const bytes = Base64.toUint8Array(b64);
|
||||
term.write(bytes);
|
||||
@@ -120,7 +109,7 @@ if (window.__FRESSH_XTERM_BRIDGE__) {
|
||||
|
||||
case 'setFont': {
|
||||
const { family, size } = msg;
|
||||
const patch: Partial<import('@xterm/xterm').ITerminalOptions> = {};
|
||||
const patch: Partial<ITerminalOptions> = {};
|
||||
if (family) patch.fontFamily = family;
|
||||
if (typeof size === 'number') patch.fontSize = size;
|
||||
if (Object.keys(patch).length) {
|
||||
@@ -136,7 +125,7 @@ if (window.__FRESSH_XTERM_BRIDGE__) {
|
||||
|
||||
case 'setTheme': {
|
||||
const { background, foreground } = msg;
|
||||
const theme: Partial<import('@xterm/xterm').ITheme> = {};
|
||||
const theme: Partial<ITheme> = {};
|
||||
if (background) {
|
||||
theme.background = background;
|
||||
document.body.style.backgroundColor = background;
|
||||
@@ -153,20 +142,44 @@ if (window.__FRESSH_XTERM_BRIDGE__) {
|
||||
}
|
||||
|
||||
case 'setOptions': {
|
||||
const opts = msg.opts ?? {};
|
||||
const { cursorBlink, scrollback, fontFamily, fontSize } = opts;
|
||||
const patch: Partial<import('@xterm/xterm').ITerminalOptions> = {};
|
||||
if (typeof cursorBlink === 'boolean') patch.cursorBlink = cursorBlink;
|
||||
if (typeof scrollback === 'number') patch.scrollback = scrollback;
|
||||
if (fontFamily) patch.fontFamily = fontFamily;
|
||||
if (typeof fontSize === 'number') patch.fontSize = fontSize;
|
||||
const incoming = (msg.opts ?? {}) as Record<string, unknown>;
|
||||
type PatchRecord = Partial<
|
||||
Record<
|
||||
keyof ITerminalOptions,
|
||||
ITerminalOptions[keyof ITerminalOptions]
|
||||
>
|
||||
>;
|
||||
const patch: PatchRecord = {};
|
||||
for (const [k, v] of Object.entries(incoming)) {
|
||||
// Avoid touching cols/rows via options setters here
|
||||
if (k === 'cols' || k === 'rows') continue;
|
||||
// Theme: also mirror background to page for seamless visuals
|
||||
if (k === 'theme' && v && typeof v === 'object') {
|
||||
const theme = v as ITheme;
|
||||
if (theme.background) {
|
||||
document.body.style.backgroundColor = theme.background;
|
||||
}
|
||||
patch.theme = theme;
|
||||
continue;
|
||||
}
|
||||
const key = k as keyof ITerminalOptions;
|
||||
patch[key] = v as ITerminalOptions[keyof ITerminalOptions];
|
||||
}
|
||||
if (Object.keys(patch).length) {
|
||||
term.options = patch;
|
||||
post({
|
||||
type: 'debug',
|
||||
message: `setOptions(${Object.keys(patch).join(',')})`,
|
||||
});
|
||||
if (patch.fontFamily || patch.fontSize) fitAddon.fit();
|
||||
// If dimensions-affecting options changed, refit
|
||||
if (
|
||||
patch.fontFamily !== undefined ||
|
||||
patch.fontSize !== undefined ||
|
||||
patch.letterSpacing !== undefined ||
|
||||
patch.lineHeight !== undefined
|
||||
) {
|
||||
fitAddon.fit();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
25
packages/react-native-xtermjs-webview/src/bridge.ts
Normal file
25
packages/react-native-xtermjs-webview/src/bridge.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
type ITerminalOptions = import('@xterm/xterm').ITerminalOptions;
|
||||
|
||||
// Messages posted from the WebView (xterm page) to React Native
|
||||
export type BridgeInboundMessage =
|
||||
| { type: 'initialized' }
|
||||
| { type: 'input'; b64: string }
|
||||
| { type: 'debug'; message: string };
|
||||
|
||||
// Messages injected from React Native into the WebView (xterm page)
|
||||
export type BridgeOutboundMessage =
|
||||
| { type: 'write'; b64: string }
|
||||
| { type: 'write'; chunks: string[] }
|
||||
| { type: 'resize'; cols?: number; rows?: number }
|
||||
| { type: 'setFont'; family?: string; size?: number }
|
||||
| { type: 'setTheme'; background?: string; foreground?: string }
|
||||
| { type: 'setOptions'; opts: Partial<ITerminalOptions> }
|
||||
| { type: 'clear' }
|
||||
| { type: 'focus' };
|
||||
|
||||
export type TerminalOptionsPatch = BridgeOutboundMessage extends {
|
||||
type: 'setOptions';
|
||||
opts: infer O;
|
||||
}
|
||||
? O
|
||||
: never;
|
||||
@@ -1,33 +1,31 @@
|
||||
type ITerminalOptions = import('@xterm/xterm').ITerminalOptions;
|
||||
import { Base64 } from 'js-base64';
|
||||
import React, { useEffect, useImperativeHandle, useRef } from 'react';
|
||||
import { WebView } from 'react-native-webview';
|
||||
import htmlString from '../dist-internal/index.html?raw';
|
||||
import { Base64 } from 'js-base64';
|
||||
import {
|
||||
type BridgeInboundMessage,
|
||||
type BridgeOutboundMessage,
|
||||
type TerminalOptionsPatch,
|
||||
} from './bridge';
|
||||
// Re-exported shared types live in src/bridge.ts for library build
|
||||
// Internal page imports the same file via ../src/bridge
|
||||
|
||||
type StrictOmit<T, K extends keyof T> = Omit<T, K>;
|
||||
|
||||
type InboundMessage =
|
||||
| { type: 'initialized' }
|
||||
| { type: 'input'; b64: string } // user typed data from xterm -> RN
|
||||
| { type: 'debug'; message: string };
|
||||
/**
|
||||
* Message from the webview to RN
|
||||
*/
|
||||
type InboundMessage = BridgeInboundMessage;
|
||||
|
||||
type OutboundMessage =
|
||||
| { type: 'write'; b64: string }
|
||||
| { type: 'write'; chunks: string[] }
|
||||
| { type: 'resize'; cols?: number; rows?: number }
|
||||
| { type: 'setFont'; family?: string; size?: number }
|
||||
| { type: 'setTheme'; background?: string; foreground?: string }
|
||||
| {
|
||||
type: 'setOptions';
|
||||
opts: Partial<{
|
||||
cursorBlink: boolean;
|
||||
scrollback: number;
|
||||
fontFamily: string;
|
||||
fontSize: number;
|
||||
}>;
|
||||
}
|
||||
| { type: 'clear' }
|
||||
| { type: 'focus' };
|
||||
/**
|
||||
* Message from RN to the webview
|
||||
*/
|
||||
type OutboundMessage = BridgeOutboundMessage;
|
||||
|
||||
/**
|
||||
* Message from this pkg to calling RN
|
||||
*/
|
||||
export type XtermInbound =
|
||||
| { type: 'initialized' }
|
||||
| { type: 'data'; data: Uint8Array }
|
||||
@@ -41,11 +39,7 @@ export type XtermWebViewHandle = {
|
||||
resize: (cols?: number, rows?: number) => void;
|
||||
setFont: (family?: string, size?: number) => void;
|
||||
setTheme: (background?: string, foreground?: string) => void;
|
||||
setOptions: (
|
||||
opts: OutboundMessage extends { type: 'setOptions'; opts: infer O }
|
||||
? O
|
||||
: never,
|
||||
) => void;
|
||||
setOptions: (opts: TerminalOptionsPatch) => void;
|
||||
clear: () => void;
|
||||
focus: () => void;
|
||||
};
|
||||
@@ -58,24 +52,14 @@ export interface XtermJsWebViewProps
|
||||
ref: React.RefObject<XtermWebViewHandle | null>;
|
||||
onMessage?: (msg: XtermInbound) => void;
|
||||
|
||||
// xterm-ish props
|
||||
fontFamily?: string;
|
||||
fontSize?: number;
|
||||
cursorBlink?: boolean;
|
||||
scrollback?: number;
|
||||
themeBackground?: string;
|
||||
themeForeground?: string;
|
||||
// xterm Terminal.setOptions props (typed from @xterm/xterm)
|
||||
options?: Partial<ITerminalOptions>;
|
||||
}
|
||||
|
||||
export function XtermJsWebView({
|
||||
ref,
|
||||
onMessage,
|
||||
fontFamily,
|
||||
fontSize,
|
||||
cursorBlink,
|
||||
scrollback,
|
||||
themeBackground,
|
||||
themeForeground,
|
||||
options,
|
||||
...props
|
||||
}: XtermJsWebViewProps) {
|
||||
const webRef = useRef<WebView>(null);
|
||||
@@ -161,25 +145,39 @@ export function XtermJsWebView({
|
||||
};
|
||||
}, []);
|
||||
|
||||
// Push initial options/theme whenever props change
|
||||
// Apply options changes via setOptions without remounting
|
||||
const prevOptsRef = useRef<Partial<ITerminalOptions> | null>(null);
|
||||
useEffect(() => {
|
||||
const opts: Record<string, unknown> = {};
|
||||
if (typeof cursorBlink === 'boolean') opts.cursorBlink = cursorBlink;
|
||||
if (typeof scrollback === 'number') opts.scrollback = scrollback;
|
||||
if (fontFamily) opts.fontFamily = fontFamily;
|
||||
if (typeof fontSize === 'number') opts.fontSize = fontSize;
|
||||
if (Object.keys(opts).length) send({ type: 'setOptions', opts });
|
||||
}, [cursorBlink, scrollback, fontFamily, fontSize]);
|
||||
const merged: Partial<ITerminalOptions> = {
|
||||
...(options ?? {}),
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (themeBackground || themeForeground) {
|
||||
send({
|
||||
type: 'setTheme',
|
||||
background: themeBackground,
|
||||
foreground: themeForeground,
|
||||
});
|
||||
// Compute shallow patch of changed keys to reduce noise
|
||||
const prev: Partial<ITerminalOptions> = (prevOptsRef.current ??
|
||||
{}) as Partial<ITerminalOptions>;
|
||||
type PatchRecord = Partial<
|
||||
Record<keyof ITerminalOptions, ITerminalOptions[keyof ITerminalOptions]>
|
||||
>;
|
||||
const patch: PatchRecord = {};
|
||||
const keys = new Set<string>([
|
||||
...Object.keys(prev as object),
|
||||
...Object.keys(merged as object),
|
||||
]);
|
||||
let changed = false;
|
||||
for (const k of keys) {
|
||||
const key = k as keyof ITerminalOptions;
|
||||
const prevVal = prev[key];
|
||||
const nextVal = merged[key];
|
||||
if (prevVal !== nextVal) {
|
||||
patch[key] = nextVal as ITerminalOptions[keyof ITerminalOptions];
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}, [themeBackground, themeForeground]);
|
||||
if (changed) {
|
||||
send({ type: 'setOptions', opts: patch });
|
||||
prevOptsRef.current = merged;
|
||||
}
|
||||
}, [options]);
|
||||
|
||||
return (
|
||||
<WebView
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import { defineConfig } from 'vite';
|
||||
import react from '@vitejs/plugin-react';
|
||||
import fs from 'fs';
|
||||
import { resolve } from 'path';
|
||||
import react from '@vitejs/plugin-react';
|
||||
import { defineConfig } from 'vite';
|
||||
import dts from 'vite-plugin-dts';
|
||||
|
||||
const logExternal: boolean = false;
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
react(),
|
||||
@@ -22,11 +25,14 @@ export default defineConfig({
|
||||
build: {
|
||||
sourcemap: true,
|
||||
rollupOptions: {
|
||||
external: ['react', 'react/jsx-runtime', 'react-native-webview'],
|
||||
// external: () => {
|
||||
// fs.writeFileSync('dep.log', `${dep}\n`, { flag: 'a' });
|
||||
// return false;
|
||||
// }
|
||||
// Externalize all non-relative, non-absolute imports (i.e. dependencies)
|
||||
// Keep only our own sources and the raw internal HTML in the bundle.
|
||||
external: (id) => {
|
||||
if (logExternal) fs.writeFileSync('dep.log', `${id}\n`, { flag: 'a' });
|
||||
const isRelative = id.startsWith('.') || id.startsWith('/');
|
||||
const isInternalHtml = id.includes('dist-internal/index.html?raw');
|
||||
return !isRelative && !isInternalHtml;
|
||||
},
|
||||
},
|
||||
lib: {
|
||||
entry: resolve(__dirname, 'src/index.tsx'),
|
||||
|
||||
12
pnpm-lock.yaml
generated
12
pnpm-lock.yaml
generated
@@ -367,6 +367,12 @@ importers:
|
||||
'@epic-web/config':
|
||||
specifier: ^1.21.3
|
||||
version: 1.21.3(@typescript-eslint/utils@8.44.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint@9.35.0(jiti@2.5.1))(prettier-plugin-astro@0.14.1)(prettier-plugin-organize-imports@4.2.0(prettier@3.6.2)(typescript@5.9.2))(prettier@3.6.2)(typescript@5.9.2)
|
||||
'@eslint-community/eslint-plugin-eslint-comments':
|
||||
specifier: ^4.5.0
|
||||
version: 4.5.0(eslint@9.35.0(jiti@2.5.1))
|
||||
'@eslint-react/eslint-plugin':
|
||||
specifier: ^1.53.0
|
||||
version: 1.53.1(eslint@9.35.0(jiti@2.5.1))(ts-api-utils@2.1.0(typescript@5.9.2))(typescript@5.9.2)
|
||||
'@eslint/js':
|
||||
specifier: ^9.35.0
|
||||
version: 9.35.0
|
||||
@@ -376,6 +382,12 @@ importers:
|
||||
'@types/react-dom':
|
||||
specifier: ^19.1.7
|
||||
version: 19.1.9(@types/react@19.1.12)
|
||||
'@typescript-eslint/parser':
|
||||
specifier: ^8.44.0
|
||||
version: 8.44.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2)
|
||||
'@typescript-eslint/utils':
|
||||
specifier: ^8.43.0
|
||||
version: 8.44.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2)
|
||||
'@vitejs/plugin-react':
|
||||
specifier: ^5.0.2
|
||||
version: 5.0.2(vite@6.3.6(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.1))
|
||||
|
||||
Reference in New Issue
Block a user