feat(stage-tamagotchi): built-in local loopback http server
This commit is contained in:
@@ -87,6 +87,7 @@
|
||||
"alien-signals": "catalog:",
|
||||
"animejs": "^4.3.6",
|
||||
"async-mutex": "catalog:",
|
||||
"chess.js": "catalog:",
|
||||
"colorjs.io": "^0.6.1",
|
||||
"culori": "^4.0.2",
|
||||
"d3": "catalog:",
|
||||
@@ -127,6 +128,7 @@
|
||||
"vue-router": "^5.0.4",
|
||||
"vue-sonner": "catalog:",
|
||||
"web-haptics": "catalog:",
|
||||
"whatwg-mimetype": "catalog:",
|
||||
"xsschema": "catalog:",
|
||||
"zod": "^4.3.6"
|
||||
},
|
||||
@@ -165,6 +167,7 @@
|
||||
"@types/semver": "^7.7.1",
|
||||
"@types/splitpanes": "catalog:",
|
||||
"@types/three": "^0.184.0",
|
||||
"@types/whatwg-mimetype": "catalog:",
|
||||
"@types/yauzl": "^2.10.3",
|
||||
"@vitejs/plugin-vue": "^6.0.6",
|
||||
"@vue-macros/volar": "^3.1.2",
|
||||
|
||||
@@ -25,6 +25,7 @@ import { setElectronMainDirname } from './libs/electron/location'
|
||||
import { createI18n } from './libs/i18n'
|
||||
import { createWindowAuthManagerService } from './services/airi/auth'
|
||||
import { setupServerChannel } from './services/airi/channel-server'
|
||||
import { setupBuiltInServer } from './services/airi/http-server'
|
||||
import { setupMcpStdioManager } from './services/airi/mcp-servers'
|
||||
import { setupPluginHost } from './services/airi/plugins'
|
||||
import { setupAutoUpdater } from './services/electron/auto-updater'
|
||||
@@ -125,6 +126,10 @@ app.whenReady().then(async () => {
|
||||
build: async ({ dependsOn }) => setupServerChannel(dependsOn),
|
||||
})
|
||||
|
||||
const airiHttpServer = injeca.provide('modules:airi-http-server', {
|
||||
build: async () => setupBuiltInServer({ servers: [] }),
|
||||
})
|
||||
|
||||
const mcpStdioManager = injeca.provide('modules:mcp-stdio-manager', {
|
||||
build: async () => setupMcpStdioManager(),
|
||||
})
|
||||
@@ -187,7 +192,7 @@ app.whenReady().then(async () => {
|
||||
})
|
||||
|
||||
injeca.invoke({
|
||||
dependsOn: { mainWindow, tray, serverChannel, pluginHost, mcpStdioManager, onboardingWindow: onboardingWindowManager },
|
||||
dependsOn: { mainWindow, tray, serverChannel, airiHttpServer, pluginHost, mcpStdioManager, onboardingWindow: onboardingWindowManager },
|
||||
callback: noop,
|
||||
})
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
electronAuthLogout,
|
||||
electronAuthStartLogin,
|
||||
} from '../../../shared/eventa'
|
||||
import { startLoopbackServer } from '../electron/loopback-server'
|
||||
import { startLoopbackServer } from './http-server/http/auth'
|
||||
|
||||
const log = useLogg('auth-service').useGlobalConfig()
|
||||
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
import { HTTPError } from 'h3'
|
||||
|
||||
export interface HttpErrorInput {
|
||||
status: number
|
||||
code: string
|
||||
message: string
|
||||
reason?: string
|
||||
details?: unknown
|
||||
expose?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Unified HTTP error shape for AIRI local HTTP server modules.
|
||||
*
|
||||
* Use when:
|
||||
* - Returning typed errors from internal server modules
|
||||
* - Keeping stable error code + reason metadata for logging/debugging
|
||||
*
|
||||
* Expects:
|
||||
* - `code` is a stable machine-readable value
|
||||
* - `message` is safe for clients when `expose` is true
|
||||
*
|
||||
* Returns:
|
||||
* - Error instance with status and structured metadata
|
||||
*/
|
||||
export class HttpError extends Error {
|
||||
readonly status: number
|
||||
readonly code: string
|
||||
readonly reason?: string
|
||||
readonly details?: unknown
|
||||
readonly expose: boolean
|
||||
|
||||
constructor(input: HttpErrorInput) {
|
||||
super(input.message)
|
||||
this.name = 'HttpError'
|
||||
this.status = input.status
|
||||
this.code = input.code
|
||||
this.reason = input.reason
|
||||
this.details = input.details
|
||||
this.expose = input.expose ?? false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a local `HttpError` into an h3-compatible throwable error.
|
||||
*
|
||||
* Use when:
|
||||
* - A route catches internal `HttpError` values and must throw an h3 HTTP error
|
||||
*
|
||||
* Expects:
|
||||
* - `error.message` is only exposed when `error.expose` is true
|
||||
*
|
||||
* Returns:
|
||||
* - `HTTPError` preserving status while controlling client-visible message
|
||||
*/
|
||||
export function toH3HttpError(error: HttpError) {
|
||||
return HTTPError.status(error.status, error.expose ? error.message : defaultHttpMessage(error.status))
|
||||
}
|
||||
|
||||
function defaultHttpMessage(status: number) {
|
||||
if (status === 400)
|
||||
return 'Bad Request'
|
||||
if (status === 401)
|
||||
return 'Unauthorized'
|
||||
if (status === 403)
|
||||
return 'Forbidden'
|
||||
if (status === 404)
|
||||
return 'Not Found'
|
||||
if (status >= 500)
|
||||
return 'Internal Server Error'
|
||||
return 'Request Failed'
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import { afterEach, describe, expect, it } from 'vitest'
|
||||
|
||||
import { startLoopbackServer } from './index'
|
||||
|
||||
describe('startLoopbackServer', () => {
|
||||
const servers: Array<Awaited<ReturnType<typeof startLoopbackServer>>> = []
|
||||
|
||||
afterEach(async () => {
|
||||
for (const server of servers.splice(0)) {
|
||||
server.close()
|
||||
await server.result.catch(() => {})
|
||||
}
|
||||
})
|
||||
|
||||
it('returns code and state from callback', async () => {
|
||||
const server = await startLoopbackServer()
|
||||
servers.push(server)
|
||||
|
||||
const response = await fetch(`http://127.0.0.1:${server.port}/callback?code=ok&state=state-1`)
|
||||
expect(response.status).toBe(200)
|
||||
|
||||
await expect(server.result).resolves.toEqual({ code: 'ok', state: 'state-1' })
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,131 @@
|
||||
import { eventHandler, getQuery, H3, handleCors } from 'h3'
|
||||
|
||||
import { createH3Server } from '../../server'
|
||||
|
||||
export interface LoopbackCallbackResult {
|
||||
code: string
|
||||
state: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a temporary loopback callback server for the Electron OIDC flow.
|
||||
*
|
||||
* Use when:
|
||||
* - Exchanging authorization code from system browser callback
|
||||
*
|
||||
* Expects:
|
||||
* - Callback request on `GET /callback?code=...&state=...`
|
||||
* - One-shot lifecycle; first successful callback closes the server
|
||||
*
|
||||
* Returns:
|
||||
* - Random bound port, callback result promise, and manual cancellation method
|
||||
*/
|
||||
export async function startLoopbackServer(): Promise<{
|
||||
port: number
|
||||
result: Promise<LoopbackCallbackResult>
|
||||
close: () => void
|
||||
}> {
|
||||
const host = '127.0.0.1'
|
||||
let settled = false
|
||||
let timeout: ReturnType<typeof setTimeout> | undefined
|
||||
|
||||
let resolveResult!: (value: LoopbackCallbackResult) => void
|
||||
let rejectResult!: (reason: Error) => void
|
||||
|
||||
const result = new Promise<LoopbackCallbackResult>((resolve, reject) => {
|
||||
resolveResult = resolve
|
||||
rejectResult = reject
|
||||
})
|
||||
|
||||
const app = new H3()
|
||||
const loopbackServer = createH3Server({ app, host })
|
||||
const corsOptions = {
|
||||
origin: '*',
|
||||
methods: '*',
|
||||
preflight: {
|
||||
statusCode: 204,
|
||||
},
|
||||
} as const
|
||||
|
||||
const finish = (callback: () => void) => {
|
||||
if (settled) {
|
||||
return
|
||||
}
|
||||
|
||||
settled = true
|
||||
if (timeout) {
|
||||
clearTimeout(timeout)
|
||||
timeout = undefined
|
||||
}
|
||||
callback()
|
||||
void loopbackServer.stop()
|
||||
}
|
||||
|
||||
app.options('/callback', eventHandler(async (event) => {
|
||||
const corsResponse = handleCors(event, corsOptions)
|
||||
if (corsResponse !== false) {
|
||||
return corsResponse
|
||||
}
|
||||
|
||||
return new Response(null, { status: 204 })
|
||||
}))
|
||||
|
||||
app.get('/callback', eventHandler(async (event) => {
|
||||
const corsResponse = handleCors(event, corsOptions)
|
||||
if (corsResponse !== false) {
|
||||
return corsResponse
|
||||
}
|
||||
|
||||
const query = getQuery(event)
|
||||
const error = typeof query.error === 'string' ? query.error : undefined
|
||||
if (error) {
|
||||
const description = typeof query.error_description === 'string' && query.error_description.length > 0
|
||||
? query.error_description
|
||||
: error
|
||||
finish(() => {
|
||||
rejectResult(new Error(description))
|
||||
})
|
||||
return new Response('<html><body><h2>Authentication failed</h2><p>You can close this window.</p></body></html>', {
|
||||
status: 200,
|
||||
headers: { 'Content-Type': 'text/html; charset=utf-8' },
|
||||
})
|
||||
}
|
||||
|
||||
const code = typeof query.code === 'string' ? query.code : ''
|
||||
const state = typeof query.state === 'string' ? query.state : ''
|
||||
|
||||
if (!code || !state) {
|
||||
return new Response('<html><body><h2>Missing parameters</h2></body></html>', {
|
||||
status: 400,
|
||||
headers: { 'Content-Type': 'text/html; charset=utf-8' },
|
||||
})
|
||||
}
|
||||
|
||||
finish(() => {
|
||||
resolveResult({ code, state })
|
||||
})
|
||||
|
||||
return new Response('<html><body><h2>Authentication successful!</h2><p>You can close this window and return to the app.</p></body></html>', {
|
||||
status: 200,
|
||||
headers: { 'Content-Type': 'text/html; charset=utf-8' },
|
||||
})
|
||||
}))
|
||||
|
||||
const address = await loopbackServer.start()
|
||||
|
||||
timeout = setTimeout(() => {
|
||||
finish(() => {
|
||||
rejectResult(new Error('Sign-in timed out — no callback received'))
|
||||
})
|
||||
}, 5 * 60 * 1000)
|
||||
|
||||
return {
|
||||
port: address.port,
|
||||
result,
|
||||
close: () => {
|
||||
finish(() => {
|
||||
rejectResult(new Error('OIDC sign-in attempt cancelled'))
|
||||
})
|
||||
},
|
||||
}
|
||||
}
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
import { mkdir, mkdtemp, writeFile } from 'node:fs/promises'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
|
||||
import { afterEach, describe, expect, it } from 'vitest'
|
||||
|
||||
import { createExtensionStaticAssetServer } from './index'
|
||||
import { createExtensionAssetTokenStore } from './token-store'
|
||||
|
||||
describe('createExtensionStaticAssetServer', () => {
|
||||
const servers: Array<ReturnType<typeof createExtensionStaticAssetServer>> = []
|
||||
|
||||
afterEach(async () => {
|
||||
while (servers.length > 0) {
|
||||
const server = servers.pop()
|
||||
if (!server) {
|
||||
continue
|
||||
}
|
||||
await server.stop()
|
||||
}
|
||||
})
|
||||
|
||||
it('accepts pathPrefix relative to /ui route segment', async () => {
|
||||
const rootDir = await mkdtemp(join(tmpdir(), 'airi-extension-static-assets-'))
|
||||
await mkdir(join(rootDir, 'ui', 'assets'), { recursive: true })
|
||||
await writeFile(join(rootDir, 'ui', 'assets', 'app.js'), 'console.log("ok")\n')
|
||||
|
||||
const extensionId = 'airi-plugin-game-chess'
|
||||
const version = '1.0.0'
|
||||
const tokenStore = createExtensionAssetTokenStore()
|
||||
const validateInputs: string[] = []
|
||||
const server = createExtensionStaticAssetServer({
|
||||
getManifestEntryByName: () => new Map([
|
||||
[extensionId, { rootDir, version }],
|
||||
]),
|
||||
tokenStore: {
|
||||
...tokenStore,
|
||||
validate(token, input) {
|
||||
validateInputs.push(input.assetPath)
|
||||
return tokenStore.validate(token, input)
|
||||
},
|
||||
},
|
||||
})
|
||||
servers.push(server)
|
||||
await server.start()
|
||||
|
||||
const token = server.issueToken({
|
||||
extensionId,
|
||||
version,
|
||||
sessionId: 'session-1',
|
||||
pathPrefix: 'assets/',
|
||||
ttlMs: 60_000,
|
||||
})
|
||||
|
||||
const baseUrl = server.getBaseUrl()
|
||||
expect(baseUrl).toBeTruthy()
|
||||
|
||||
const response = await fetch(`${baseUrl}/_airi/extensions/${extensionId}/ui/assets/app.js?t=${token}`)
|
||||
const responseBody = await response.text()
|
||||
expect({
|
||||
status: response.status,
|
||||
validateInputs,
|
||||
responseBody,
|
||||
}).toEqual({
|
||||
status: 200,
|
||||
validateInputs: ['assets/app.js'],
|
||||
responseBody: 'console.log("ok")\n',
|
||||
})
|
||||
})
|
||||
})
|
||||
+179
@@ -0,0 +1,179 @@
|
||||
import type { ServerManager } from '../../server-manager/types'
|
||||
import type { ExtensionAssetTokenStore } from './types'
|
||||
|
||||
import { stat } from 'node:fs/promises'
|
||||
|
||||
import { H3 } from 'h3'
|
||||
|
||||
import {
|
||||
normalizePluginAssetPath,
|
||||
resolvePluginAssetFilePath,
|
||||
} from '../../../plugins/asset-mount'
|
||||
import { HttpError } from '../../errors'
|
||||
import { createH3Server } from '../../server'
|
||||
import { createExtensionStaticAssetRoute } from './route'
|
||||
import { createExtensionAssetTokenStore } from './token-store'
|
||||
|
||||
export interface ExtensionStaticAssetManifestEntry {
|
||||
rootDir: string
|
||||
version: string
|
||||
}
|
||||
|
||||
export interface ExtensionStaticAssetServer extends ServerManager {
|
||||
getBaseUrl: () => string | undefined
|
||||
issueToken: ExtensionAssetTokenStore['issue']
|
||||
revokeByExtensionId: ExtensionAssetTokenStore['revokeByExtensionId']
|
||||
revokeAll: ExtensionAssetTokenStore['revokeAll']
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the standalone extension static asset server.
|
||||
*
|
||||
* Use when:
|
||||
* - Main process must serve plugin iframe assets via local loopback HTTP
|
||||
* - Tokenized auth is required for all plugin asset requests
|
||||
*
|
||||
* Expects:
|
||||
* - `getManifestEntryByName` returns up-to-date plugin root/version map
|
||||
*
|
||||
* Returns:
|
||||
* - Lifecycle service with token issue/revoke APIs and local base URL getter
|
||||
*/
|
||||
export function createExtensionStaticAssetServer(options: {
|
||||
getManifestEntryByName: () => Map<string, ExtensionStaticAssetManifestEntry>
|
||||
host?: string
|
||||
tokenStore?: ExtensionAssetTokenStore
|
||||
getType?: (ext: string) => string | undefined
|
||||
}): ExtensionStaticAssetServer {
|
||||
const host = options.host ?? '127.0.0.1'
|
||||
const tokenStore = options.tokenStore ?? createExtensionAssetTokenStore()
|
||||
const getType = options.getType ?? defaultExtensionAssetMimeTypeResolver
|
||||
|
||||
const app = new H3()
|
||||
const serverLifecycle = createH3Server({ app, host })
|
||||
|
||||
app.get('/_airi/extensions/**', createExtensionStaticAssetRoute({
|
||||
getType,
|
||||
authorize: async ({ token, extensionId, assetPath }) => {
|
||||
const entry = options.getManifestEntryByName().get(extensionId)
|
||||
if (!entry) {
|
||||
return {
|
||||
ok: false,
|
||||
error: new HttpError({
|
||||
status: 401,
|
||||
code: 'EXTENSION_ASSET_EXTENSION_NOT_REGISTERED',
|
||||
message: 'Unauthorized',
|
||||
reason: 'extension manifest entry does not exist for requested extensionId',
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
return tokenStore.validate(token, {
|
||||
extensionId,
|
||||
version: entry.version,
|
||||
assetPath,
|
||||
})
|
||||
},
|
||||
resolveAsset: async ({ extensionId, assetPath }) => {
|
||||
const entry = options.getManifestEntryByName().get(extensionId)
|
||||
if (!entry) {
|
||||
return {
|
||||
ok: false,
|
||||
error: new HttpError({
|
||||
status: 404,
|
||||
code: 'EXTENSION_ASSET_EXTENSION_NOT_FOUND',
|
||||
message: 'Not Found',
|
||||
reason: 'extension manifest entry does not exist for requested extensionId',
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
const normalizedAssetPath = normalizePluginAssetPath(assetPath)
|
||||
if (!normalizedAssetPath) {
|
||||
return {
|
||||
ok: false,
|
||||
error: new HttpError({
|
||||
status: 400,
|
||||
code: 'EXTENSION_ASSET_PATH_INVALID',
|
||||
message: 'Bad Request',
|
||||
reason: 'asset path could not be normalized',
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
const fullAssetPath = `ui/${normalizedAssetPath}`
|
||||
const filePath = await resolvePluginAssetFilePath(entry.rootDir, fullAssetPath)
|
||||
if (!filePath) {
|
||||
return {
|
||||
ok: false,
|
||||
error: new HttpError({
|
||||
status: 400,
|
||||
code: 'EXTENSION_ASSET_PATH_RESOLVE_FAILED',
|
||||
message: 'Bad Request',
|
||||
reason: 'resolved asset path is outside extension root',
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const fileStats = await stat(filePath)
|
||||
if (!fileStats.isFile()) {
|
||||
return {
|
||||
ok: false,
|
||||
error: new HttpError({
|
||||
status: 404,
|
||||
code: 'EXTENSION_ASSET_NOT_FILE',
|
||||
message: 'Not Found',
|
||||
reason: 'resolved path exists but is not a file',
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
ok: true,
|
||||
filePath,
|
||||
size: fileStats.size,
|
||||
mtime: fileStats.mtimeMs,
|
||||
}
|
||||
}
|
||||
catch {
|
||||
return {
|
||||
ok: false,
|
||||
error: new HttpError({
|
||||
status: 404,
|
||||
code: 'EXTENSION_ASSET_NOT_FOUND',
|
||||
message: 'Not Found',
|
||||
reason: 'resolved file does not exist',
|
||||
}),
|
||||
}
|
||||
}
|
||||
},
|
||||
}))
|
||||
|
||||
return {
|
||||
key: 'extension-static-assets',
|
||||
async start() {
|
||||
await serverLifecycle.start()
|
||||
},
|
||||
async stop() {
|
||||
await serverLifecycle.stop()
|
||||
},
|
||||
getBaseUrl() {
|
||||
return serverLifecycle.getAddress()?.baseUrl
|
||||
},
|
||||
issueToken: tokenStore.issue,
|
||||
revokeByExtensionId: tokenStore.revokeByExtensionId,
|
||||
revokeAll: tokenStore.revokeAll,
|
||||
}
|
||||
}
|
||||
|
||||
const extensionAssetMimeTypeOverrides: Record<string, string> = {
|
||||
'.wasm': 'application/wasm',
|
||||
'.avif': 'image/avif',
|
||||
'.heic': 'image/heic',
|
||||
'.heif': 'image/heif',
|
||||
}
|
||||
|
||||
function defaultExtensionAssetMimeTypeResolver(ext: string) {
|
||||
return extensionAssetMimeTypeOverrides[ext.toLowerCase()]
|
||||
}
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
import { mkdtemp, rm, writeFile } from 'node:fs/promises'
|
||||
import { createServer } from 'node:http'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
|
||||
import { H3 } from 'h3'
|
||||
import { toNodeHandler } from 'h3/node'
|
||||
import { afterEach, describe, expect, it } from 'vitest'
|
||||
|
||||
import { HttpError } from '../../errors'
|
||||
import { createExtensionStaticAssetRoute } from './route'
|
||||
|
||||
describe('createExtensionStaticAssetRoute', () => {
|
||||
let server: ReturnType<typeof createServer> | undefined
|
||||
const tempRoots: string[] = []
|
||||
|
||||
afterEach(async () => {
|
||||
for (const root of tempRoots) {
|
||||
await rm(root, { recursive: true, force: true })
|
||||
}
|
||||
tempRoots.length = 0
|
||||
|
||||
await new Promise<void>((resolve) => {
|
||||
if (!server) {
|
||||
resolve()
|
||||
return
|
||||
}
|
||||
|
||||
server.close(() => resolve())
|
||||
server = undefined
|
||||
})
|
||||
})
|
||||
|
||||
it('returns 401 when token is missing', async () => {
|
||||
const app = new H3()
|
||||
app.get('/_airi/extensions/:extensionId/ui/**assetPath', createExtensionStaticAssetRoute({
|
||||
authorize: async () => ({
|
||||
ok: false,
|
||||
error: new HttpError({
|
||||
status: 401,
|
||||
code: 'TOKEN_MISSING',
|
||||
message: 'Unauthorized',
|
||||
}),
|
||||
}),
|
||||
resolveAsset: async () => ({
|
||||
ok: false,
|
||||
error: new HttpError({
|
||||
status: 404,
|
||||
code: 'NOT_FOUND',
|
||||
message: 'Not Found',
|
||||
}),
|
||||
}),
|
||||
}))
|
||||
|
||||
server = createServer(toNodeHandler(app))
|
||||
await new Promise<void>(resolve => server!.listen(0, '127.0.0.1', () => resolve()))
|
||||
const address = server.address()
|
||||
const port = typeof address === 'object' && address ? address.port : 0
|
||||
|
||||
const response = await fetch(`http://127.0.0.1:${port}/_airi/extensions/a/ui/index.html`)
|
||||
expect(response.status).toBe(401)
|
||||
})
|
||||
|
||||
it('uses custom getType resolver and sets nosniff', async () => {
|
||||
const root = await mkdtemp(join(tmpdir(), 'airi-extension-static-assets-'))
|
||||
tempRoots.push(root)
|
||||
|
||||
const wasmFilePath = join(root, 'module.wasm')
|
||||
await writeFile(wasmFilePath, new Uint8Array([0, 97, 115, 109]))
|
||||
|
||||
let authorizeCalled = false
|
||||
const app = new H3()
|
||||
app.get('/_airi/extensions/:extensionId/ui/:assetPath', createExtensionStaticAssetRoute({
|
||||
authorize: async () => {
|
||||
authorizeCalled = true
|
||||
return { ok: true }
|
||||
},
|
||||
resolveAsset: async () => ({
|
||||
ok: true,
|
||||
filePath: wasmFilePath,
|
||||
size: 4,
|
||||
mtime: Date.now(),
|
||||
}),
|
||||
getType: ext => ext === '.wasm' ? 'application/wasm' : undefined,
|
||||
}))
|
||||
|
||||
server = createServer(toNodeHandler(app))
|
||||
await new Promise<void>(resolve => server!.listen(0, '127.0.0.1', () => resolve()))
|
||||
const address = server.address()
|
||||
const port = typeof address === 'object' && address ? address.port : 0
|
||||
|
||||
const response = await fetch(`http://127.0.0.1:${port}/_airi/extensions/a/ui/module.wasm?t=test-token`)
|
||||
if (!authorizeCalled) {
|
||||
throw new Error(`Expected authorize to be called. response=${response.status} body=${await response.text()}`)
|
||||
}
|
||||
expect(response.status).toBe(200)
|
||||
expect(response.headers.get('content-type')).toBe('application/wasm')
|
||||
expect(response.headers.get('x-content-type-options')).toBe('nosniff')
|
||||
})
|
||||
})
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
import type { ExtensionStaticAssetRouteOptions } from './types'
|
||||
|
||||
import { readFile } from 'node:fs/promises'
|
||||
|
||||
import { eventHandler, getQuery, getRequestURL, serveStatic } from 'h3'
|
||||
|
||||
import {
|
||||
normalizePluginAssetPath,
|
||||
parsePluginAssetRequestPath,
|
||||
} from '../../../plugins/asset-mount'
|
||||
import { HttpError, toH3HttpError } from '../../errors'
|
||||
|
||||
/**
|
||||
* Creates the secured extension static asset route handler.
|
||||
*
|
||||
* Use when:
|
||||
* - Serving plugin iframe assets under `/_airi/extensions/:extensionId/ui/**assetPath`
|
||||
*
|
||||
* Expects:
|
||||
* - Query token `t` to be present and valid
|
||||
* - `resolveAsset` to map request params into a validated local file
|
||||
*
|
||||
* Returns:
|
||||
* - H3 event handler that enforces token auth before static file response
|
||||
*/
|
||||
export function createExtensionStaticAssetRoute(options: ExtensionStaticAssetRouteOptions) {
|
||||
return eventHandler(async (event) => {
|
||||
try {
|
||||
const requestPath = parsePluginAssetRequestPath(getRequestURL(event).pathname)
|
||||
const extensionId = requestPath?.extensionId ?? ''
|
||||
const assetPath = normalizePluginAssetPath(requestPath?.assetPath ?? '')
|
||||
const queryToken = getQuery(event).t
|
||||
const token = typeof queryToken === 'string' ? queryToken : ''
|
||||
|
||||
if (!token || !extensionId || !assetPath) {
|
||||
throw new HttpError({
|
||||
status: 401,
|
||||
code: 'EXTENSION_ASSET_REQUEST_INVALID',
|
||||
message: 'Unauthorized',
|
||||
reason: 'required token, extensionId, or assetPath is missing',
|
||||
})
|
||||
}
|
||||
|
||||
const auth = await options.authorize({ token, extensionId, assetPath })
|
||||
if (!auth.ok) {
|
||||
throw auth.error
|
||||
}
|
||||
|
||||
let resolved: Awaited<ReturnType<ExtensionStaticAssetRouteOptions['resolveAsset']>> | undefined
|
||||
const resolveOnce = async () => {
|
||||
if (!resolved) {
|
||||
resolved = await options.resolveAsset({ extensionId, assetPath })
|
||||
}
|
||||
return resolved
|
||||
}
|
||||
|
||||
return await serveStatic(event, {
|
||||
getType: options.getType,
|
||||
getContents: async () => {
|
||||
const item = await resolveOnce()
|
||||
if (!item.ok) {
|
||||
throw item.error
|
||||
}
|
||||
return await readFile(item.filePath)
|
||||
},
|
||||
getMeta: async () => {
|
||||
const item = await resolveOnce()
|
||||
if (!item.ok) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
event.res.headers.set('X-Content-Type-Options', 'nosniff')
|
||||
return {
|
||||
size: item.size,
|
||||
mtime: item.mtime,
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
catch (error) {
|
||||
if (error instanceof HttpError) {
|
||||
throw toH3HttpError(error)
|
||||
}
|
||||
throw error
|
||||
}
|
||||
})
|
||||
}
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import { createExtensionAssetTokenStore } from './token-store'
|
||||
|
||||
describe('createExtensionAssetTokenStore', () => {
|
||||
it('issues and validates short token', () => {
|
||||
const now = vi.fn(() => 1000)
|
||||
const store = createExtensionAssetTokenStore({ now })
|
||||
|
||||
const token = store.issue({
|
||||
extensionId: 'airi-plugin-game-chess',
|
||||
version: '0.1.0',
|
||||
sessionId: 'session-1',
|
||||
pathPrefix: 'ui/',
|
||||
ttlMs: 30_000,
|
||||
})
|
||||
|
||||
expect(token.length).toBeLessThanOrEqual(24)
|
||||
expect(store.validate(token, {
|
||||
extensionId: 'airi-plugin-game-chess',
|
||||
version: '0.1.0',
|
||||
assetPath: 'ui/index.html',
|
||||
}).ok).toBe(true)
|
||||
})
|
||||
|
||||
it('rejects mismatched plugin and revoked tokens', () => {
|
||||
const now = vi.fn(() => 1000)
|
||||
const store = createExtensionAssetTokenStore({ now })
|
||||
|
||||
const token = store.issue({
|
||||
extensionId: 'airi-plugin-game-chess',
|
||||
version: '0.1.0',
|
||||
sessionId: 'session-1',
|
||||
pathPrefix: 'ui/',
|
||||
ttlMs: 30_000,
|
||||
})
|
||||
|
||||
expect(store.validate(token, {
|
||||
extensionId: 'other-plugin',
|
||||
version: '0.1.0',
|
||||
assetPath: 'ui/index.html',
|
||||
})).toMatchObject({
|
||||
ok: false,
|
||||
error: {
|
||||
status: 401,
|
||||
code: 'EXTENSION_ASSET_EXTENSION_MISMATCH',
|
||||
},
|
||||
})
|
||||
|
||||
store.revokeByExtensionId('airi-plugin-game-chess')
|
||||
|
||||
expect(store.validate(token, {
|
||||
extensionId: 'airi-plugin-game-chess',
|
||||
version: '0.1.0',
|
||||
assetPath: 'ui/index.html',
|
||||
})).toMatchObject({
|
||||
ok: false,
|
||||
error: {
|
||||
status: 401,
|
||||
code: 'EXTENSION_ASSET_TOKEN_NOT_FOUND',
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
+127
@@ -0,0 +1,127 @@
|
||||
import type {
|
||||
ExtensionAssetTokenIssueInput,
|
||||
ExtensionAssetTokenStore,
|
||||
ExtensionAssetTokenValidateInput,
|
||||
ExtensionAssetTokenValidationResult,
|
||||
} from './types'
|
||||
|
||||
import { randomBytes } from 'node:crypto'
|
||||
|
||||
import { HttpError } from '../../errors'
|
||||
|
||||
interface ExtensionAssetTokenRecord {
|
||||
extensionId: string
|
||||
version: string
|
||||
sessionId: string
|
||||
pathPrefix: string
|
||||
exp: number
|
||||
}
|
||||
|
||||
function normalizePathPrefix(pathPrefix: string) {
|
||||
const normalized = pathPrefix.trim().replaceAll('\\', '/')
|
||||
if (!normalized) {
|
||||
return ''
|
||||
}
|
||||
|
||||
return normalized.endsWith('/') ? normalized : `${normalized}/`
|
||||
}
|
||||
|
||||
function createOpaqueToken() {
|
||||
return randomBytes(18).toString('base64url')
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an in-memory opaque token store for extension static asset access.
|
||||
*
|
||||
* Use when:
|
||||
* - Main process needs short-lived auth tokens for plugin iframe asset loading
|
||||
*
|
||||
* Expects:
|
||||
* - Tokens are opaque and stored server-side only
|
||||
* - Callers revoke by plugin id or revoke all on app shutdown
|
||||
*
|
||||
* Returns:
|
||||
* - Issue/validate/revoke operations for extension asset tokens
|
||||
*/
|
||||
export function createExtensionAssetTokenStore(options: { now?: () => number } = {}): ExtensionAssetTokenStore {
|
||||
const now = options.now ?? (() => Date.now())
|
||||
const records = new Map<string, ExtensionAssetTokenRecord>()
|
||||
|
||||
const dropIfExpired = (token: string, record: ExtensionAssetTokenRecord) => {
|
||||
if (record.exp > now()) {
|
||||
return false
|
||||
}
|
||||
|
||||
records.delete(token)
|
||||
return true
|
||||
}
|
||||
|
||||
const issue = (input: ExtensionAssetTokenIssueInput) => {
|
||||
const token = createOpaqueToken()
|
||||
records.set(token, {
|
||||
extensionId: input.extensionId,
|
||||
version: input.version,
|
||||
sessionId: input.sessionId,
|
||||
pathPrefix: normalizePathPrefix(input.pathPrefix),
|
||||
exp: now() + input.ttlMs,
|
||||
})
|
||||
return token
|
||||
}
|
||||
|
||||
const unauthorized = (code: string, reason: string) => {
|
||||
return {
|
||||
ok: false as const,
|
||||
error: new HttpError({
|
||||
status: 401,
|
||||
code,
|
||||
message: 'Unauthorized',
|
||||
reason,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
const validate = (token: string, input: ExtensionAssetTokenValidateInput): ExtensionAssetTokenValidationResult => {
|
||||
const record = records.get(token)
|
||||
if (!record) {
|
||||
return unauthorized('EXTENSION_ASSET_TOKEN_NOT_FOUND', 'token was not found in token store')
|
||||
}
|
||||
|
||||
if (dropIfExpired(token, record)) {
|
||||
return unauthorized('EXTENSION_ASSET_TOKEN_EXPIRED', 'token has expired')
|
||||
}
|
||||
|
||||
if (record.extensionId !== input.extensionId) {
|
||||
return unauthorized('EXTENSION_ASSET_EXTENSION_MISMATCH', 'token extensionId does not match request extensionId')
|
||||
}
|
||||
|
||||
if (typeof input.version === 'string' && record.version !== input.version) {
|
||||
return unauthorized('EXTENSION_ASSET_VERSION_MISMATCH', 'token version does not match request version')
|
||||
}
|
||||
|
||||
const normalizedAssetPath = input.assetPath.trim().replaceAll('\\', '/')
|
||||
if (!normalizedAssetPath) {
|
||||
return unauthorized('EXTENSION_ASSET_PATH_EMPTY', 'asset path is empty')
|
||||
}
|
||||
|
||||
if (record.pathPrefix && !normalizedAssetPath.startsWith(record.pathPrefix)) {
|
||||
return unauthorized('EXTENSION_ASSET_PATH_PREFIX_MISMATCH', 'asset path is outside allowed prefix')
|
||||
}
|
||||
|
||||
return { ok: true }
|
||||
}
|
||||
|
||||
return {
|
||||
issue,
|
||||
validate,
|
||||
revokeByExtensionId(extensionId) {
|
||||
for (const [token, record] of records.entries()) {
|
||||
if (record.extensionId === extensionId) {
|
||||
records.delete(token)
|
||||
}
|
||||
}
|
||||
},
|
||||
revokeAll() {
|
||||
records.clear()
|
||||
},
|
||||
}
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
import type { HttpError } from '../../errors'
|
||||
|
||||
export interface ExtensionAssetTokenIssueInput {
|
||||
extensionId: string
|
||||
version: string
|
||||
sessionId: string
|
||||
pathPrefix: string
|
||||
ttlMs: number
|
||||
}
|
||||
|
||||
export interface ExtensionAssetTokenValidateInput {
|
||||
extensionId: string
|
||||
version?: string
|
||||
assetPath: string
|
||||
}
|
||||
|
||||
export type ExtensionAssetTokenValidationResult
|
||||
= | { ok: true }
|
||||
| { ok: false, error: HttpError }
|
||||
|
||||
export interface ExtensionAssetTokenStore {
|
||||
issue: (input: ExtensionAssetTokenIssueInput) => string
|
||||
validate: (token: string, input: ExtensionAssetTokenValidateInput) => ExtensionAssetTokenValidationResult
|
||||
revokeByExtensionId: (extensionId: string) => void
|
||||
revokeAll: () => void
|
||||
}
|
||||
|
||||
export type ExtensionStaticAssetResolveResult
|
||||
= | { ok: true, filePath: string, size: number, mtime: number }
|
||||
| { ok: false, error: HttpError }
|
||||
|
||||
export interface ExtensionStaticAssetRouteOptions {
|
||||
authorize: (params: { token: string, extensionId: string, assetPath: string }) => Promise<ExtensionAssetTokenValidationResult>
|
||||
resolveAsset: (params: { extensionId: string, assetPath: string }) => Promise<ExtensionStaticAssetResolveResult>
|
||||
getType?: (ext: string) => string | undefined
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import { setupBuiltInServer } from './index'
|
||||
|
||||
describe('setupBuiltInServer', () => {
|
||||
it('starts registered adapters', async () => {
|
||||
const auth = { key: 'auth', start: vi.fn(async () => {}), stop: vi.fn(async () => {}) }
|
||||
const assets = { key: 'assets', start: vi.fn(async () => {}), stop: vi.fn(async () => {}) }
|
||||
|
||||
const service = setupBuiltInServer({
|
||||
authServer: auth,
|
||||
extensionStaticAssetServer: assets,
|
||||
})
|
||||
|
||||
await service.start()
|
||||
|
||||
expect(auth.start).toHaveBeenCalledOnce()
|
||||
expect(assets.start).toHaveBeenCalledOnce()
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,38 @@
|
||||
import type { ServerManager } from './server-manager/types'
|
||||
|
||||
import { createHttpServerManager } from './server-manager'
|
||||
|
||||
export interface BuiltInServer {
|
||||
start: () => Promise<void>
|
||||
stop: () => Promise<void>
|
||||
}
|
||||
|
||||
/**
|
||||
* Composes AIRI local HTTP servers behind one lifecycle service.
|
||||
*
|
||||
* Use when:
|
||||
* - Main process needs one start/stop entrypoint for local HTTP services
|
||||
*
|
||||
* Expects:
|
||||
* - Each server follows the `ServerManager` lifecycle contract
|
||||
*
|
||||
* Returns:
|
||||
* - A lifecycle service with ordered startup/shutdown behavior
|
||||
*/
|
||||
export function setupBuiltInServer(params: {
|
||||
authServer?: ServerManager
|
||||
extensionStaticAssetServer?: ServerManager
|
||||
servers?: ServerManager[]
|
||||
}): BuiltInServer {
|
||||
const servers = [
|
||||
...(params.authServer ? [params.authServer] : []),
|
||||
...(params.extensionStaticAssetServer ? [params.extensionStaticAssetServer] : []),
|
||||
...(params.servers ?? []),
|
||||
]
|
||||
const manager = createHttpServerManager(servers)
|
||||
|
||||
return {
|
||||
start: manager.start,
|
||||
stop: manager.stop,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import { createHttpServerManager } from './index'
|
||||
|
||||
describe('createHttpServerManager', () => {
|
||||
it('starts and stops registered servers in order', async () => {
|
||||
const startA = vi.fn(async () => {})
|
||||
const stopA = vi.fn(async () => {})
|
||||
const startB = vi.fn(async () => {})
|
||||
const stopB = vi.fn(async () => {})
|
||||
|
||||
const manager = createHttpServerManager([
|
||||
{ key: 'a', start: startA, stop: stopA },
|
||||
{ key: 'b', start: startB, stop: stopB },
|
||||
])
|
||||
|
||||
await manager.start()
|
||||
await manager.stop()
|
||||
|
||||
expect(startA).toHaveBeenCalledOnce()
|
||||
expect(startB).toHaveBeenCalledOnce()
|
||||
expect(stopB).toHaveBeenCalledOnce()
|
||||
expect(stopA).toHaveBeenCalledOnce()
|
||||
})
|
||||
|
||||
it('serializes concurrent start and stop calls', async () => {
|
||||
let releaseStartA: (() => void) | undefined
|
||||
|
||||
const startA = vi.fn(async () => {
|
||||
await new Promise<void>((resolve) => {
|
||||
releaseStartA = resolve
|
||||
})
|
||||
})
|
||||
const stopA = vi.fn(async () => {})
|
||||
const startB = vi.fn(async () => {})
|
||||
const stopB = vi.fn(async () => {})
|
||||
|
||||
const manager = createHttpServerManager([
|
||||
{ key: 'a', start: startA, stop: stopA },
|
||||
{ key: 'b', start: startB, stop: stopB },
|
||||
])
|
||||
|
||||
const firstStart = manager.start()
|
||||
const secondStart = manager.start()
|
||||
await Promise.resolve()
|
||||
|
||||
expect(startA).toHaveBeenCalledOnce()
|
||||
expect(startB).toHaveBeenCalledTimes(0)
|
||||
|
||||
releaseStartA?.()
|
||||
await Promise.all([firstStart, secondStart])
|
||||
|
||||
expect(startA).toHaveBeenCalledOnce()
|
||||
expect(startB).toHaveBeenCalledOnce()
|
||||
|
||||
await Promise.all([manager.stop(), manager.stop()])
|
||||
|
||||
expect(stopB).toHaveBeenCalledOnce()
|
||||
expect(stopA).toHaveBeenCalledOnce()
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,51 @@
|
||||
import type { ServerManager } from './types'
|
||||
|
||||
import { Mutex } from 'async-mutex'
|
||||
|
||||
/**
|
||||
* Creates an ordered lifecycle manager for AIRI local HTTP servers.
|
||||
*
|
||||
* Use when:
|
||||
* - Multiple standalone HTTP servers must boot together
|
||||
* - Shutdown order must run in reverse startup order
|
||||
*
|
||||
* Expects:
|
||||
* - The `servers` list order defines startup order
|
||||
* - `start`/`stop` may be called multiple times safely
|
||||
*
|
||||
* Returns:
|
||||
* - An idempotent manager with `start` and `stop`
|
||||
*/
|
||||
export function createHttpServerManager(servers: ServerManager[]) {
|
||||
let started = false
|
||||
const lifecycleMutex = new Mutex()
|
||||
|
||||
return {
|
||||
async start() {
|
||||
await lifecycleMutex.runExclusive(async () => {
|
||||
if (started) {
|
||||
return
|
||||
}
|
||||
|
||||
for (const server of servers) {
|
||||
await server.start()
|
||||
}
|
||||
|
||||
started = true
|
||||
})
|
||||
},
|
||||
async stop() {
|
||||
await lifecycleMutex.runExclusive(async () => {
|
||||
if (!started) {
|
||||
return
|
||||
}
|
||||
|
||||
for (const server of [...servers].reverse()) {
|
||||
await server.stop()
|
||||
}
|
||||
|
||||
started = false
|
||||
})
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* Shared lifecycle contract for AIRI local HTTP sub-servers.
|
||||
*
|
||||
* Use when:
|
||||
* - Registering standalone local HTTP services under `services/airi/http-server`
|
||||
* - Composing startup/shutdown order in the server manager
|
||||
*
|
||||
* Expects:
|
||||
* - `start` to be idempotent
|
||||
* - `stop` to be safe to call after partial startup
|
||||
*
|
||||
* Returns:
|
||||
* - Promise lifecycle completion for each server action
|
||||
*/
|
||||
export interface ServerManager {
|
||||
key: string
|
||||
start: () => Promise<void>
|
||||
stop: () => Promise<void>
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
import { Mutex } from 'async-mutex'
|
||||
import { getRandomPort } from 'get-port-please'
|
||||
import { serve } from 'h3'
|
||||
|
||||
export interface BuiltInServerAddress {
|
||||
host: string
|
||||
port: number
|
||||
baseUrl: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a reusable local HTTP server lifecycle around an h3 app.
|
||||
*
|
||||
* Use when:
|
||||
* - A local HTTP module needs host/port assignment and start/stop lifecycle
|
||||
* - Callers may trigger concurrent start/stop operations
|
||||
*
|
||||
* Expects:
|
||||
* - `app` is a valid h3 app/handler accepted by `serve`
|
||||
* - Caller manages route registration before first `start`
|
||||
*
|
||||
* Returns:
|
||||
* - Idempotent lifecycle with serialized start/stop and runtime address getter
|
||||
*/
|
||||
export function createH3Server(options: {
|
||||
app: Parameters<typeof serve>[0]
|
||||
host?: string
|
||||
port?: number
|
||||
silent?: boolean
|
||||
}) {
|
||||
const host = options.host ?? '127.0.0.1'
|
||||
const silent = options.silent ?? true
|
||||
const lifecycleMutex = new Mutex()
|
||||
|
||||
let server: ReturnType<typeof serve> | undefined
|
||||
let address: BuiltInServerAddress | undefined
|
||||
|
||||
return {
|
||||
async start(): Promise<BuiltInServerAddress> {
|
||||
return await lifecycleMutex.runExclusive(async () => {
|
||||
if (address) {
|
||||
return address
|
||||
}
|
||||
|
||||
const port = options.port ?? await getRandomPort(host)
|
||||
server = serve(options.app, { hostname: host, port, silent })
|
||||
|
||||
address = {
|
||||
host,
|
||||
port,
|
||||
baseUrl: `http://${host}:${port}`,
|
||||
}
|
||||
|
||||
return address
|
||||
})
|
||||
},
|
||||
async stop(): Promise<void> {
|
||||
await lifecycleMutex.runExclusive(async () => {
|
||||
address = undefined
|
||||
if (!server) {
|
||||
return
|
||||
}
|
||||
|
||||
const activeServer = server
|
||||
server = undefined
|
||||
await activeServer.close().catch(() => {})
|
||||
})
|
||||
},
|
||||
getAddress() {
|
||||
return address
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
|
||||
import { afterEach, describe, expect, it } from 'vitest'
|
||||
|
||||
import {
|
||||
buildMountedPluginAssetPath,
|
||||
normalizePluginAssetPath,
|
||||
parsePluginAssetRequestPath,
|
||||
resolvePluginAssetFilePath,
|
||||
} from './asset-mount'
|
||||
|
||||
describe('asset-mount', () => {
|
||||
const tempRoots: string[] = []
|
||||
|
||||
afterEach(async () => {
|
||||
for (const root of tempRoots) {
|
||||
await rm(root, { recursive: true, force: true })
|
||||
}
|
||||
tempRoots.length = 0
|
||||
})
|
||||
|
||||
it('normalizes valid asset paths and rejects traversal-like segments', () => {
|
||||
expect(normalizePluginAssetPath('dist/ui/index.html')).toBe('dist/ui/index.html')
|
||||
expect(normalizePluginAssetPath('./dist/ui/index.html')).toBeUndefined()
|
||||
expect(normalizePluginAssetPath('../secret.txt')).toBeUndefined()
|
||||
expect(normalizePluginAssetPath('dist/../ui/index.html')).toBeUndefined()
|
||||
})
|
||||
|
||||
it('parses mounted plugin request path and rejects malformed routes', () => {
|
||||
expect(parsePluginAssetRequestPath('/_airi/extensions/airi-plugin-game-chess/ui/dist/ui/index.html')).toEqual({
|
||||
extensionId: 'airi-plugin-game-chess',
|
||||
assetPath: 'dist/ui/index.html',
|
||||
})
|
||||
expect(parsePluginAssetRequestPath('/_airi/extensions/airi-plugin-game-chess/ui/../../etc/passwd')).toBeUndefined()
|
||||
expect(parsePluginAssetRequestPath('/_airi/extensions//ui/index.html')).toBeUndefined()
|
||||
})
|
||||
|
||||
it('builds mounted asset path with encoded segments', () => {
|
||||
expect(buildMountedPluginAssetPath({
|
||||
extensionId: 'airi-plugin-game-chess',
|
||||
assetPath: 'dist/ui/index.html',
|
||||
})).toBe('/_airi/extensions/airi-plugin-game-chess/ui/dist/ui/index.html')
|
||||
})
|
||||
|
||||
it('resolves only files inside plugin root', async () => {
|
||||
const root = await mkdtemp(join(tmpdir(), 'airi-plugin-assets-'))
|
||||
tempRoots.push(root)
|
||||
|
||||
await mkdir(join(root, 'dist', 'ui'), { recursive: true })
|
||||
await writeFile(join(root, 'dist', 'ui', 'index.html'), '<html></html>')
|
||||
|
||||
await expect(resolvePluginAssetFilePath(root, 'dist/ui/index.html')).resolves.toContain('dist/ui/index.html')
|
||||
await expect(resolvePluginAssetFilePath(root, '../outside.txt')).resolves.toBeUndefined()
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,100 @@
|
||||
import { realpath } from 'node:fs/promises'
|
||||
import { resolve, sep } from 'node:path'
|
||||
|
||||
export interface ParsedPluginAssetRequest {
|
||||
extensionId: string
|
||||
assetPath: string
|
||||
}
|
||||
|
||||
const pathPrefix = '/_airi/extensions/'
|
||||
const segmentPattern = /^[\w.+-]+$/
|
||||
|
||||
export function normalizePluginAssetPath(value: string): string | undefined {
|
||||
const normalized = value.trim().replaceAll('\\', '/')
|
||||
if (!normalized) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const segments = normalized
|
||||
.split('/')
|
||||
.map(segment => decodeURIComponent(segment).trim())
|
||||
.filter(Boolean)
|
||||
|
||||
if (segments.length === 0) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
if (segments.some(segment => segment === '.' || segment === '..')) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
return segments.join('/')
|
||||
}
|
||||
|
||||
export function parsePluginAssetRequestPath(pathname: string): ParsedPluginAssetRequest | undefined {
|
||||
if (!pathname.startsWith(pathPrefix)) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const rawRemainder = pathname.slice(pathPrefix.length)
|
||||
if (!rawRemainder) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const segments = rawRemainder.split('/').filter(Boolean)
|
||||
if (segments.length < 3) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const extensionId = decodeURIComponent(segments[0] ?? '')
|
||||
const mountSegment = decodeURIComponent(segments[1] ?? '')
|
||||
const rawAssetPath = segments.slice(2).join('/')
|
||||
|
||||
if (!segmentPattern.test(extensionId) || mountSegment !== 'ui') {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const assetPath = normalizePluginAssetPath(rawAssetPath)
|
||||
if (!assetPath) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
return {
|
||||
extensionId,
|
||||
assetPath,
|
||||
}
|
||||
}
|
||||
|
||||
export async function resolvePluginAssetFilePath(rootDir: string, assetPath: string) {
|
||||
const normalizedAssetPath = normalizePluginAssetPath(assetPath)
|
||||
if (!normalizedAssetPath) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const resolvedRoot = await realpath(rootDir)
|
||||
const resolvedCandidate = resolve(resolvedRoot, normalizedAssetPath)
|
||||
const normalizedRootPrefix = `${resolvedRoot}${sep}`
|
||||
if (resolvedCandidate !== resolvedRoot && !resolvedCandidate.startsWith(normalizedRootPrefix)) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
return resolvedCandidate
|
||||
}
|
||||
|
||||
export function buildMountedPluginAssetPath(input: {
|
||||
extensionId: string
|
||||
assetPath: string
|
||||
}) {
|
||||
const normalizedAssetPath = normalizePluginAssetPath(input.assetPath)
|
||||
if (!normalizedAssetPath) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const encodedExtensionId = encodeURIComponent(input.extensionId)
|
||||
const encodedAssetPath = normalizedAssetPath
|
||||
.split('/')
|
||||
.map(segment => encodeURIComponent(segment))
|
||||
.join('/')
|
||||
|
||||
return `${pathPrefix}${encodedExtensionId}/ui/${encodedAssetPath}`
|
||||
}
|
||||
@@ -1,146 +0,0 @@
|
||||
import http from 'node:http'
|
||||
|
||||
import { useLogg } from '@guiiai/logg'
|
||||
|
||||
const log = useLogg('loopback-server').useGlobalConfig()
|
||||
|
||||
export interface LoopbackCallbackResult {
|
||||
code: string
|
||||
state: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Fixed ports for the loopback OIDC callback server.
|
||||
* The server relay page (`/api/auth/oidc/electron-callback`) forwards the
|
||||
* authorization code to `http://127.0.0.1:{port}/callback` via JS fetch().
|
||||
* The port is encoded in the `state` parameter, not in the redirect_uri.
|
||||
*
|
||||
* See RFC 8252 S7.3 for the loopback redirect pattern.
|
||||
*/
|
||||
const LOOPBACK_PORTS = [19721, 19722, 19723, 19724, 19725]
|
||||
|
||||
/**
|
||||
* Start a temporary HTTP server on 127.0.0.1 to receive an OIDC authorization
|
||||
* callback. Tries ports from LOOPBACK_PORTS in order. The server handles
|
||||
* exactly one request and then shuts down.
|
||||
*
|
||||
* This follows RFC 8252 S7.3 (Loopback Interface Redirection) and is the
|
||||
* approach used by VS Code, GitHub Desktop, and Slack Desktop.
|
||||
*/
|
||||
export function startLoopbackServer(): Promise<{
|
||||
port: number
|
||||
result: Promise<LoopbackCallbackResult>
|
||||
close: () => void
|
||||
}> {
|
||||
return new Promise((resolveStart, rejectStart) => {
|
||||
let settled = false
|
||||
let resultResolve: (value: LoopbackCallbackResult) => void
|
||||
let resultReject: (reason: Error) => void
|
||||
|
||||
const resultPromise = new Promise<LoopbackCallbackResult>((resolve, reject) => {
|
||||
resultResolve = resolve
|
||||
resultReject = reject
|
||||
})
|
||||
|
||||
const server = http.createServer((req, res) => {
|
||||
if (settled)
|
||||
return
|
||||
|
||||
const url = new URL(req.url ?? '/', `http://127.0.0.1`)
|
||||
|
||||
// CORS: the relay page on the server origin sends a cross-origin fetch()
|
||||
// to the loopback. Allow all origins since this is a one-shot local server.
|
||||
res.setHeader('Access-Control-Allow-Origin', '*')
|
||||
res.setHeader('Access-Control-Allow-Methods', 'GET, OPTIONS')
|
||||
if (req.method === 'OPTIONS') {
|
||||
res.writeHead(204)
|
||||
res.end()
|
||||
return
|
||||
}
|
||||
|
||||
if (url.pathname !== '/callback') {
|
||||
res.writeHead(404)
|
||||
res.end('Not found')
|
||||
return
|
||||
}
|
||||
|
||||
const error = url.searchParams.get('error')
|
||||
if (error) {
|
||||
const description = url.searchParams.get('error_description') ?? error
|
||||
settled = true
|
||||
res.writeHead(200, { 'Content-Type': 'text/html' })
|
||||
res.end('<html><body><h2>Authentication failed</h2><p>You can close this window.</p></body></html>')
|
||||
resultReject!(new Error(description))
|
||||
server.close()
|
||||
return
|
||||
}
|
||||
|
||||
const code = url.searchParams.get('code')
|
||||
const state = url.searchParams.get('state')
|
||||
|
||||
if (!code || !state) {
|
||||
res.writeHead(400, { 'Content-Type': 'text/html' })
|
||||
res.end('<html><body><h2>Missing parameters</h2></body></html>')
|
||||
return
|
||||
}
|
||||
|
||||
settled = true
|
||||
res.writeHead(200, { 'Content-Type': 'text/html' })
|
||||
res.end('<html><body><h2>Authentication successful!</h2><p>You can close this window and return to the app.</p></body></html>')
|
||||
resultResolve!({ code, state })
|
||||
server.close()
|
||||
})
|
||||
|
||||
// Safety timeout: close after 5 minutes
|
||||
const timeout = setTimeout(() => {
|
||||
if (!settled) {
|
||||
settled = true
|
||||
resultReject!(new Error('Sign-in timed out — no callback received'))
|
||||
server.close()
|
||||
}
|
||||
}, 5 * 60 * 1000)
|
||||
|
||||
server.on('close', () => clearTimeout(timeout))
|
||||
|
||||
// Try each port in order
|
||||
let portIndex = 0
|
||||
|
||||
function tryListen(): void {
|
||||
if (portIndex >= LOOPBACK_PORTS.length) {
|
||||
rejectStart(new Error(`All loopback ports (${LOOPBACK_PORTS.join(', ')}) are in use`))
|
||||
return
|
||||
}
|
||||
|
||||
const port = LOOPBACK_PORTS[portIndex]
|
||||
|
||||
server.once('error', (err: NodeJS.ErrnoException) => {
|
||||
if (err.code === 'EADDRINUSE') {
|
||||
log.withFields({ port }).log('Port in use, trying next')
|
||||
portIndex++
|
||||
tryListen()
|
||||
}
|
||||
else {
|
||||
rejectStart(err)
|
||||
}
|
||||
})
|
||||
|
||||
server.listen(port, '127.0.0.1', () => {
|
||||
log.withFields({ port }).log('Loopback callback server started')
|
||||
resolveStart({
|
||||
port,
|
||||
result: resultPromise,
|
||||
close: () => {
|
||||
if (settled)
|
||||
return
|
||||
|
||||
settled = true
|
||||
resultReject!(new Error('OIDC sign-in attempt cancelled'))
|
||||
server.close()
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
tryListen()
|
||||
})
|
||||
}
|
||||
Generated
+293
-236
@@ -6,6 +6,9 @@ settings:
|
||||
|
||||
catalogs:
|
||||
default:
|
||||
'@ax-llm/ax':
|
||||
specifier: ^19.0.43
|
||||
version: 19.0.45
|
||||
'@better-auth/oauth-provider':
|
||||
specifier: 1.5.6
|
||||
version: 1.5.6
|
||||
@@ -123,6 +126,9 @@ catalogs:
|
||||
'@types/unist':
|
||||
specifier: ^3.0.3
|
||||
version: 3.0.3
|
||||
'@types/whatwg-mimetype':
|
||||
specifier: ^5.0.0
|
||||
version: 5.0.0
|
||||
'@types/ws':
|
||||
specifier: ^8.18.1
|
||||
version: 8.18.1
|
||||
@@ -183,6 +189,9 @@ catalogs:
|
||||
capacitor-native-settings:
|
||||
specifier: ^8.1.0
|
||||
version: 8.1.0
|
||||
chess.js:
|
||||
specifier: ^1.4.0
|
||||
version: 1.4.0
|
||||
crossws:
|
||||
specifier: ^0.4.5
|
||||
version: 0.4.5
|
||||
@@ -267,12 +276,18 @@ catalogs:
|
||||
posthog-js:
|
||||
specifier: 1.306.1
|
||||
version: 1.306.1
|
||||
reka-ui:
|
||||
specifier: ^2.9.2
|
||||
version: 2.9.6
|
||||
splitpanes:
|
||||
specifier: ^4.0.4
|
||||
version: 4.0.4
|
||||
std-env:
|
||||
specifier: ^4.1.0
|
||||
version: 4.1.0
|
||||
stockfish:
|
||||
specifier: ^18.0.7
|
||||
version: 18.0.7
|
||||
superjson:
|
||||
specifier: ^2.2.6
|
||||
version: 2.2.6
|
||||
@@ -297,6 +312,9 @@ catalogs:
|
||||
valibot:
|
||||
specifier: 1.2.0
|
||||
version: 1.2.0
|
||||
vieval:
|
||||
specifier: ^0.0.1
|
||||
version: 0.0.1
|
||||
vite:
|
||||
specifier: ^8.0.8
|
||||
version: 8.0.8
|
||||
@@ -318,6 +336,9 @@ catalogs:
|
||||
web-haptics:
|
||||
specifier: ^0.0.6
|
||||
version: 0.0.6
|
||||
whatwg-mimetype:
|
||||
specifier: ^5.0.0
|
||||
version: 5.0.0
|
||||
ws:
|
||||
specifier: ^8.20.0
|
||||
version: 8.20.0
|
||||
@@ -1243,6 +1264,9 @@ importers:
|
||||
async-mutex:
|
||||
specifier: 'catalog:'
|
||||
version: 0.5.0
|
||||
chess.js:
|
||||
specifier: 'catalog:'
|
||||
version: 1.4.0
|
||||
colorjs.io:
|
||||
specifier: ^0.6.1
|
||||
version: 0.6.1
|
||||
@@ -1363,6 +1387,9 @@ importers:
|
||||
web-haptics:
|
||||
specifier: 'catalog:'
|
||||
version: 0.0.6(react@19.2.3)(vue@3.5.32(typescript@5.9.3))
|
||||
whatwg-mimetype:
|
||||
specifier: 'catalog:'
|
||||
version: 5.0.0
|
||||
xsschema:
|
||||
specifier: 'catalog:'
|
||||
version: 0.5.0-beta.2(@valibot/to-json-schema@1.0.0-rc.0(valibot@1.3.1(typescript@5.9.3)))(zod-to-json-schema@3.25.2(zod@4.3.6))(zod@4.3.6)
|
||||
@@ -1375,7 +1402,7 @@ importers:
|
||||
version: 3.0.2(electron@41.2.1)
|
||||
'@electron-toolkit/tsconfig':
|
||||
specifier: ^2.0.0
|
||||
version: 2.0.0(@types/node@24.12.2)
|
||||
version: 2.0.0(@types/node@25.6.0)
|
||||
'@electron-toolkit/utils':
|
||||
specifier: ^4.0.0
|
||||
version: 4.0.0(electron@41.2.1)
|
||||
@@ -1414,7 +1441,7 @@ importers:
|
||||
version: 3.1.0
|
||||
'@intlify/unplugin-vue-i18n':
|
||||
specifier: ^11.0.7
|
||||
version: 11.0.7(@vue/compiler-dom@3.5.32)(eslint@10.2.1(jiti@2.6.1))(rollup@4.60.1)(typescript@5.9.3)(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vue-i18n@11.3.2(vue@3.5.32(typescript@5.9.3)))(vue@3.5.32(typescript@5.9.3))
|
||||
version: 11.0.7(@vue/compiler-dom@3.5.32)(eslint@10.2.1(jiti@2.6.1))(rollup@4.60.1)(typescript@5.9.3)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vue-i18n@11.3.2(vue@3.5.32(typescript@5.9.3)))(vue@3.5.32(typescript@5.9.3))
|
||||
'@modelcontextprotocol/sdk':
|
||||
specifier: 'catalog:'
|
||||
version: 1.29.0(@cfworker/json-schema@4.1.1)(zod@4.3.6)
|
||||
@@ -1450,10 +1477,10 @@ importers:
|
||||
version: link:../../packages/ui-transitions
|
||||
'@proj-airi/unplugin-fetch':
|
||||
specifier: 'catalog:'
|
||||
version: 0.2.3(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))
|
||||
version: 0.2.3(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))
|
||||
'@proj-airi/unplugin-live2d-sdk':
|
||||
specifier: ^0.1.7
|
||||
version: 0.1.7(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)
|
||||
version: 0.1.7(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)
|
||||
'@types/audioworklet':
|
||||
specifier: 'catalog:'
|
||||
version: 0.0.97
|
||||
@@ -1472,12 +1499,15 @@ importers:
|
||||
'@types/three':
|
||||
specifier: ^0.184.0
|
||||
version: 0.184.0
|
||||
'@types/whatwg-mimetype':
|
||||
specifier: 'catalog:'
|
||||
version: 5.0.0
|
||||
'@types/yauzl':
|
||||
specifier: ^2.10.3
|
||||
version: 2.10.3
|
||||
'@vitejs/plugin-vue':
|
||||
specifier: ^6.0.6
|
||||
version: 6.0.6(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vue@3.5.32(typescript@5.9.3))
|
||||
version: 6.0.6(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vue@3.5.32(typescript@5.9.3))
|
||||
'@vue-macros/volar':
|
||||
specifier: ^3.1.2
|
||||
version: 3.1.2(typescript@5.9.3)(vue-tsc@3.2.6(typescript@5.9.3))(vue@3.5.32(typescript@5.9.3))
|
||||
@@ -1507,7 +1537,7 @@ importers:
|
||||
version: 6.8.3
|
||||
electron-vite:
|
||||
specifier: ^5.0.0
|
||||
version: 5.0.0(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))
|
||||
version: 5.0.0(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))
|
||||
get-port-please:
|
||||
specifier: 'catalog:'
|
||||
version: 3.2.0
|
||||
@@ -1528,31 +1558,31 @@ importers:
|
||||
version: 2.2.6
|
||||
unocss-preset-scrollbar:
|
||||
specifier: ^4.0.0
|
||||
version: 4.0.0(unocss@66.6.8(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))
|
||||
version: 4.0.0(unocss@66.6.8(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))
|
||||
unplugin-info:
|
||||
specifier: ^1.3.2
|
||||
version: 1.3.2(esbuild@0.27.2)(rollup@4.60.1)(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))
|
||||
version: 1.3.2(esbuild@0.27.2)(rollup@4.60.1)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))
|
||||
unplugin-yaml:
|
||||
specifier: ^4.1.0
|
||||
version: 4.1.0(esbuild@0.27.2)(rolldown@1.0.0-rc.16)(rollup@4.60.1)(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))
|
||||
version: 4.1.0(@nuxt/kit@3.20.2(magicast@0.5.2))(esbuild@0.27.2)(rolldown@1.0.0-rc.16)(rollup@4.60.1)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))
|
||||
vite:
|
||||
specifier: 'catalog:'
|
||||
version: 8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)
|
||||
version: 8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)
|
||||
vite-bundle-visualizer:
|
||||
specifier: ^1.2.1
|
||||
version: 1.2.1(rolldown@1.0.0-rc.16)(rollup@4.60.1)
|
||||
vite-plugin-mkcert:
|
||||
specifier: 'catalog:'
|
||||
version: 2.0.0(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))
|
||||
version: 2.0.0(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))
|
||||
vite-plugin-vue-devtools:
|
||||
specifier: ^8.1.1
|
||||
version: 8.1.1(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vue@3.5.32(typescript@5.9.3))
|
||||
version: 8.1.1(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vue@3.5.32(typescript@5.9.3))
|
||||
vite-plugin-vue-layouts:
|
||||
specifier: ^0.11.0
|
||||
version: 0.11.0(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vue-router@5.0.4(@vue/compiler-sfc@3.5.32)(pinia@3.0.4(typescript@5.9.3)(vue@3.5.32(typescript@5.9.3)))(vue@3.5.32(typescript@5.9.3)))(vue@3.5.32(typescript@5.9.3))
|
||||
version: 0.11.0(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vue-router@5.0.4(@vue/compiler-sfc@3.5.32)(pinia@3.0.4(typescript@5.9.3)(vue@3.5.32(typescript@5.9.3)))(vue@3.5.32(typescript@5.9.3)))(vue@3.5.32(typescript@5.9.3))
|
||||
vue-macros:
|
||||
specifier: ^3.1.2
|
||||
version: 3.1.2(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(@vueuse/core@14.2.1(vue@3.5.32(typescript@5.9.3)))(esbuild@0.27.2)(rolldown@1.0.0-rc.16)(rollup@4.60.1)(typescript@5.9.3)(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vue-tsc@3.2.6(typescript@5.9.3))(vue@3.5.32(typescript@5.9.3))
|
||||
version: 3.1.2(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(@vueuse/core@14.2.1(vue@3.5.32(typescript@5.9.3)))(esbuild@0.27.2)(rolldown@1.0.0-rc.16)(rollup@4.60.1)(typescript@5.9.3)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vue-tsc@3.2.6(typescript@5.9.3))(vue@3.5.32(typescript@5.9.3))
|
||||
vue-tsc:
|
||||
specifier: ^3.2.6
|
||||
version: 3.2.6(typescript@5.9.3)
|
||||
@@ -3768,6 +3798,70 @@ importers:
|
||||
specifier: ^3.2.6
|
||||
version: 3.2.6(typescript@5.9.3)
|
||||
|
||||
plugins/airi-plugin-game-chess:
|
||||
dependencies:
|
||||
'@moeru/std':
|
||||
specifier: 'catalog:'
|
||||
version: 0.1.0-beta.17
|
||||
'@proj-airi/ui':
|
||||
specifier: workspace:^
|
||||
version: link:../../packages/ui
|
||||
animejs:
|
||||
specifier: ^4.3.6
|
||||
version: 4.3.6
|
||||
chess.js:
|
||||
specifier: 'catalog:'
|
||||
version: 1.4.0
|
||||
reka-ui:
|
||||
specifier: 'catalog:'
|
||||
version: 2.9.6(vue@3.5.32(typescript@5.9.3))
|
||||
stockfish:
|
||||
specifier: 'catalog:'
|
||||
version: 18.0.7
|
||||
vue:
|
||||
specifier: 'catalog:'
|
||||
version: 3.5.32(typescript@5.9.3)
|
||||
devDependencies:
|
||||
'@ax-llm/ax':
|
||||
specifier: 'catalog:'
|
||||
version: 19.0.45(zod@4.3.6)
|
||||
'@proj-airi/stage-ui':
|
||||
specifier: workspace:^
|
||||
version: link:../../packages/stage-ui
|
||||
'@proj-airi/unocss-preset-chromatic':
|
||||
specifier: ^1.0.2
|
||||
version: 1.1.1
|
||||
'@unocss/reset':
|
||||
specifier: ^66.6.7
|
||||
version: 66.6.8
|
||||
'@vitejs/plugin-vue':
|
||||
specifier: ^6.0.5
|
||||
version: 6.0.6(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vue@3.5.32(typescript@5.9.3))
|
||||
'@xsai-ext/providers':
|
||||
specifier: 'catalog:'
|
||||
version: 0.5.0-beta.2
|
||||
'@xsai/generate-text':
|
||||
specifier: 'catalog:'
|
||||
version: 0.5.0-beta.2(patch_hash=306bfb723913596b334140f0d6fa48063f336e3b44024efc1d72bf60d54b15e6)
|
||||
'@xsai/shared-chat':
|
||||
specifier: 'catalog:'
|
||||
version: 0.5.0-beta.2(patch_hash=26f2819b987245ec85f216b821ddf73aeb28fd7e611238a2d37250e42f838a01)
|
||||
'@xsai/stream-text':
|
||||
specifier: 'catalog:'
|
||||
version: 0.5.0-beta.2(patch_hash=90dfe10d02f5946658508ec019937eab600745c93446ce7b2fdb1a0ed70e3e49)
|
||||
pinia:
|
||||
specifier: 'catalog:'
|
||||
version: 3.0.4(typescript@5.9.3)(vue@3.5.32(typescript@5.9.3))
|
||||
unocss:
|
||||
specifier: ^66.6.7
|
||||
version: 66.6.8(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))
|
||||
vieval:
|
||||
specifier: 'catalog:'
|
||||
version: 0.0.1(@types/node@25.6.0)(chokidar@5.0.0)(dotenv@17.4.2)(esbuild@0.27.2)(giget@2.0.0)(jiti@2.6.1)(less@4.6.4)(magicast@0.5.2)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)
|
||||
vue-tsc:
|
||||
specifier: ^3.1.1
|
||||
version: 3.2.6(typescript@5.9.3)
|
||||
|
||||
plugins/airi-plugin-homeassistant:
|
||||
dependencies:
|
||||
'@guiiai/logg':
|
||||
@@ -4351,6 +4445,15 @@ packages:
|
||||
'@asamuzakjp/nwsapi@2.3.9':
|
||||
resolution: {integrity: sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==}
|
||||
|
||||
'@ax-llm/ax@19.0.45':
|
||||
resolution: {integrity: sha512-zC/OqUutTV0omOuAxBgeCihmxP3e1eUcbA78FCaMF3pPXZ3/FgQDZf0R04LH/lVIxm5BH4/qDdNNYcIVGwii8Q==}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
zod: ^3.24.0 || ^4.0.0
|
||||
peerDependenciesMeta:
|
||||
zod:
|
||||
optional: true
|
||||
|
||||
'@azure/msal-common@14.16.1':
|
||||
resolution: {integrity: sha512-nyxsA6NA4SVKh5YyRpbSXiMr7oQbwark7JU9LMeg6tJYTSPyAGkdx61wPT4gyxZfxlSxMMEyAsWaubBlNyIa1w==}
|
||||
engines: {node: '>=0.8.0'}
|
||||
@@ -9526,6 +9629,9 @@ packages:
|
||||
'@types/webxr@0.5.24':
|
||||
resolution: {integrity: sha512-h8fgEd/DpoS9CBrjEQXR+dIDraopAEfu4wYVNY2tEPwk60stPWhvZMf4Foo5FakuQ7HFZoa8WceaWFervK2Ovg==}
|
||||
|
||||
'@types/whatwg-mimetype@5.0.0':
|
||||
resolution: {integrity: sha512-YYiBDCoqBgDIF2ByYn4qDb4RaXZ46cOQ6j2We1Ni3bikFNI7YFeL8jxSiYowWsriZrb1mw09CLZ+b+ZkIhsLVw==}
|
||||
|
||||
'@types/ws@8.18.1':
|
||||
resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==}
|
||||
|
||||
@@ -11029,6 +11135,26 @@ packages:
|
||||
magicast:
|
||||
optional: true
|
||||
|
||||
c12@4.0.0-beta.4:
|
||||
resolution: {integrity: sha512-gcWQAloC/SwGx4U7l3iQdalUQQLLXwYS1d3SqIwFj4UUrTXh8L9yGkBcA00B0gxELMwbxtsrt6VrAxtSgqZZoA==}
|
||||
peerDependencies:
|
||||
chokidar: ^5
|
||||
dotenv: '*'
|
||||
giget: '*'
|
||||
jiti: '*'
|
||||
magicast: '*'
|
||||
peerDependenciesMeta:
|
||||
chokidar:
|
||||
optional: true
|
||||
dotenv:
|
||||
optional: true
|
||||
giget:
|
||||
optional: true
|
||||
jiti:
|
||||
optional: true
|
||||
magicast:
|
||||
optional: true
|
||||
|
||||
cac@6.7.14:
|
||||
resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -11112,6 +11238,9 @@ packages:
|
||||
character-entities@2.0.2:
|
||||
resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==}
|
||||
|
||||
chess.js@1.4.0:
|
||||
resolution: {integrity: sha512-BBJgrrtKQOzFLonR0l+k64A98NLemPwNsCskwb+29bRwobUa4iTm51E1kwGPbWXAcfdDa18nad6vpPPKPWarqw==}
|
||||
|
||||
chevrotain@10.5.0:
|
||||
resolution: {integrity: sha512-Pkv5rBY3+CsHOYfV5g/Vs5JY9WTHHDEKOlohI2XeygaZhUeqhAlldZ8Hz9cRmxu709bvS08YzxHdTPHhffc13A==}
|
||||
|
||||
@@ -11331,6 +11460,9 @@ packages:
|
||||
confbox@0.2.2:
|
||||
resolution: {integrity: sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==}
|
||||
|
||||
confbox@0.2.4:
|
||||
resolution: {integrity: sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==}
|
||||
|
||||
config-chain@1.1.13:
|
||||
resolution: {integrity: sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==}
|
||||
|
||||
@@ -11609,6 +11741,9 @@ packages:
|
||||
date-fns@4.1.0:
|
||||
resolution: {integrity: sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==}
|
||||
|
||||
dayjs@1.11.20:
|
||||
resolution: {integrity: sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ==}
|
||||
|
||||
de-indent@1.0.2:
|
||||
resolution: {integrity: sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==}
|
||||
|
||||
@@ -12657,9 +12792,15 @@ packages:
|
||||
fast-string-truncated-width@1.2.1:
|
||||
resolution: {integrity: sha512-Q9acT/+Uu3GwGj+5w/zsGuQjh9O1TyywhIwAxHudtWrgF09nHOPrvTLhQevPbttcxjr/SNN7mJmfOw/B1bXgow==}
|
||||
|
||||
fast-string-truncated-width@3.0.3:
|
||||
resolution: {integrity: sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g==}
|
||||
|
||||
fast-string-width@1.1.0:
|
||||
resolution: {integrity: sha512-O3fwIVIH5gKB38QNbdg+3760ZmGz0SZMgvwJbA1b2TGXceKE6A2cOlfogh1iw8lr049zPyd7YADHy+B7U4W9bQ==}
|
||||
|
||||
fast-string-width@3.0.2:
|
||||
resolution: {integrity: sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg==}
|
||||
|
||||
fast-uri@3.1.0:
|
||||
resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==}
|
||||
|
||||
@@ -15555,6 +15696,9 @@ packages:
|
||||
rc9@2.1.2:
|
||||
resolution: {integrity: sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==}
|
||||
|
||||
rc9@3.0.1:
|
||||
resolution: {integrity: sha512-gMDyleLWVE+i6Sgtc0QbbY6pEKqYs97NGi6isHQPqYlLemPoO8dxQ3uGi0f4NiP98c+jMW6cG1Kx9dDwfvqARQ==}
|
||||
|
||||
rc@1.2.8:
|
||||
resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==}
|
||||
hasBin: true
|
||||
@@ -16216,6 +16360,10 @@ packages:
|
||||
std-env@4.1.0:
|
||||
resolution: {integrity: sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==}
|
||||
|
||||
stockfish@18.0.7:
|
||||
resolution: {integrity: sha512-tJ+bfMAHs4fV7QYiLcHUicx1RzKOQwj8twx/z0iUTauMG+SE3l1rEtRSg+WER81ke3bc5tqKBWEWntUbydkgZg==}
|
||||
hasBin: true
|
||||
|
||||
store2@2.14.4:
|
||||
resolution: {integrity: sha512-srTItn1GOvyvOycgxjAnPA63FZNwy0PTyUBFMHRM+hVFltAeoh0LmNBz9SZqUS9mMqGk8rfyWyXn3GH5ReJ8Zw==}
|
||||
|
||||
@@ -17204,6 +17352,10 @@ packages:
|
||||
vfile@6.0.3:
|
||||
resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==}
|
||||
|
||||
vieval@0.0.1:
|
||||
resolution: {integrity: sha512-JCvKwQP0rE/SvvZR/vPJFBqVE286+ZxGCQpohQBtmdMuwoiE38JbkDDPlT09qx2FCckCJYdEqP05DmCxJ8X0TA==}
|
||||
hasBin: true
|
||||
|
||||
vite-bundle-visualizer@1.2.1:
|
||||
resolution: {integrity: sha512-cwz/Pg6+95YbgIDp+RPwEToc4TKxfsFWSG/tsl2DSZd9YZicUag1tQXjJ5xcL7ydvEoaC2FOZeaXOU60t9BRXw==}
|
||||
engines: {node: ^18.19.0 || >=20.6.0}
|
||||
@@ -17633,6 +17785,10 @@ packages:
|
||||
resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
whatwg-mimetype@5.0.0:
|
||||
resolution: {integrity: sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==}
|
||||
engines: {node: '>=20'}
|
||||
|
||||
whatwg-url@15.1.0:
|
||||
resolution: {integrity: sha512-2ytDk0kiEj/yu90JOAp44PVPUkO9+jVhyf+SybKlRHSDlvOOZhdPIrr7xTH64l4WixO2cP+wQIcgujkGBPPz6g==}
|
||||
engines: {node: '>=20'}
|
||||
@@ -18125,6 +18281,13 @@ snapshots:
|
||||
|
||||
'@asamuzakjp/nwsapi@2.3.9': {}
|
||||
|
||||
'@ax-llm/ax@19.0.45(zod@4.3.6)':
|
||||
dependencies:
|
||||
'@opentelemetry/api': 1.9.1
|
||||
dayjs: 1.11.20
|
||||
optionalDependencies:
|
||||
zod: 4.3.6
|
||||
|
||||
'@azure/msal-common@14.16.1': {}
|
||||
|
||||
'@azure/msal-node@2.16.3':
|
||||
@@ -19381,9 +19544,9 @@ snapshots:
|
||||
dependencies:
|
||||
electron: 41.2.1
|
||||
|
||||
'@electron-toolkit/tsconfig@2.0.0(@types/node@24.12.2)':
|
||||
'@electron-toolkit/tsconfig@2.0.0(@types/node@25.6.0)':
|
||||
dependencies:
|
||||
'@types/node': 24.12.2
|
||||
'@types/node': 25.6.0
|
||||
|
||||
'@electron-toolkit/utils@4.0.0(electron@41.2.1)':
|
||||
dependencies:
|
||||
@@ -20269,31 +20432,6 @@ snapshots:
|
||||
- supports-color
|
||||
- typescript
|
||||
|
||||
'@intlify/unplugin-vue-i18n@11.0.7(@vue/compiler-dom@3.5.32)(eslint@10.2.1(jiti@2.6.1))(rollup@4.60.1)(typescript@5.9.3)(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vue-i18n@11.3.2(vue@3.5.32(typescript@5.9.3)))(vue@3.5.32(typescript@5.9.3))':
|
||||
dependencies:
|
||||
'@eslint-community/eslint-utils': 4.9.1(eslint@10.2.1(jiti@2.6.1))
|
||||
'@intlify/bundle-utils': 11.0.7(vue-i18n@11.3.2(vue@3.5.32(typescript@5.9.3)))
|
||||
'@intlify/shared': 11.3.2
|
||||
'@intlify/vue-i18n-extensions': 8.0.0(@intlify/shared@11.3.2)(@vue/compiler-dom@3.5.32)(vue-i18n@11.3.2(vue@3.5.32(typescript@5.9.3)))(vue@3.5.32(typescript@5.9.3))
|
||||
'@rollup/pluginutils': 5.3.0(rollup@4.60.1)
|
||||
'@typescript-eslint/scope-manager': 8.58.1
|
||||
'@typescript-eslint/typescript-estree': 8.58.1(typescript@5.9.3)
|
||||
debug: 4.4.3(supports-color@10.2.2)
|
||||
fast-glob: 3.3.3
|
||||
pathe: 2.0.3
|
||||
picocolors: 1.1.1
|
||||
unplugin: 2.3.11
|
||||
vite: 8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)
|
||||
vue: 3.5.32(typescript@5.9.3)
|
||||
optionalDependencies:
|
||||
vue-i18n: 11.3.2(vue@3.5.32(typescript@5.9.3))
|
||||
transitivePeerDependencies:
|
||||
- '@vue/compiler-dom'
|
||||
- eslint
|
||||
- rollup
|
||||
- supports-color
|
||||
- typescript
|
||||
|
||||
'@intlify/unplugin-vue-i18n@11.0.7(@vue/compiler-dom@3.5.32)(eslint@10.2.1(jiti@2.6.1))(rollup@4.60.1)(typescript@5.9.3)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vue-i18n@11.3.2(vue@3.5.32(typescript@5.9.3)))(vue@3.5.32(typescript@5.9.3))':
|
||||
dependencies:
|
||||
'@eslint-community/eslint-utils': 4.9.1(eslint@10.2.1(jiti@2.6.1))
|
||||
@@ -22264,35 +22402,11 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- magicast
|
||||
|
||||
'@proj-airi/unplugin-fetch@0.2.3(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))':
|
||||
dependencies:
|
||||
ofetch: 1.5.1
|
||||
vite: 8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)
|
||||
|
||||
'@proj-airi/unplugin-fetch@0.2.3(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))':
|
||||
dependencies:
|
||||
ofetch: 1.5.1
|
||||
vite: 8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)
|
||||
|
||||
'@proj-airi/unplugin-live2d-sdk@0.1.7(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)':
|
||||
dependencies:
|
||||
ofetch: 1.5.1
|
||||
vite: 8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)
|
||||
yauzl: 3.3.0
|
||||
transitivePeerDependencies:
|
||||
- '@types/node'
|
||||
- '@vitejs/devtools'
|
||||
- esbuild
|
||||
- jiti
|
||||
- less
|
||||
- sass
|
||||
- sass-embedded
|
||||
- stylus
|
||||
- sugarss
|
||||
- terser
|
||||
- tsx
|
||||
- yaml
|
||||
|
||||
'@proj-airi/unplugin-live2d-sdk@0.1.7(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)':
|
||||
dependencies:
|
||||
ofetch: 1.5.1
|
||||
@@ -23240,7 +23354,6 @@ snapshots:
|
||||
'@types/node@25.6.0':
|
||||
dependencies:
|
||||
undici-types: 7.19.2
|
||||
optional: true
|
||||
|
||||
'@types/nprogress@0.2.3': {}
|
||||
|
||||
@@ -23324,6 +23437,8 @@ snapshots:
|
||||
|
||||
'@types/webxr@0.5.24': {}
|
||||
|
||||
'@types/whatwg-mimetype@5.0.0': {}
|
||||
|
||||
'@types/ws@8.18.1':
|
||||
dependencies:
|
||||
'@types/node': 24.12.2
|
||||
@@ -23767,6 +23882,19 @@ snapshots:
|
||||
unplugin-utils: 0.3.1
|
||||
vite: 8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)
|
||||
|
||||
'@unocss/vite@66.6.8(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))':
|
||||
dependencies:
|
||||
'@jridgewell/remapping': 2.3.5
|
||||
'@unocss/config': 66.6.8
|
||||
'@unocss/core': 66.6.8
|
||||
'@unocss/inspector': 66.6.8
|
||||
chokidar: 5.0.0
|
||||
magic-string: 0.30.21
|
||||
pathe: 2.0.3
|
||||
tinyglobby: 0.2.16
|
||||
unplugin-utils: 0.3.1
|
||||
vite: 8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)
|
||||
|
||||
'@unrteljs/eval@0.2.1':
|
||||
dependencies:
|
||||
builtin-modules: 5.0.0
|
||||
@@ -23891,12 +24019,6 @@ snapshots:
|
||||
vite: 6.4.2(@types/node@25.6.0)(jiti@2.6.1)(less@4.6.4)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)
|
||||
vue: 3.5.32(typescript@5.9.3)
|
||||
|
||||
'@vitejs/plugin-vue@6.0.6(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vue@3.5.32(typescript@5.9.3))':
|
||||
dependencies:
|
||||
'@rolldown/pluginutils': 1.0.0-rc.13
|
||||
vite: 8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)
|
||||
vue: 3.5.32(typescript@5.9.3)
|
||||
|
||||
'@vitejs/plugin-vue@6.0.6(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vue@3.5.32(typescript@5.9.3))':
|
||||
dependencies:
|
||||
'@rolldown/pluginutils': 1.0.0-rc.13
|
||||
@@ -24199,15 +24321,6 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- vue
|
||||
|
||||
'@vue-macros/devtools@3.1.2(typescript@5.9.3)(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))':
|
||||
dependencies:
|
||||
sirv: 3.0.2
|
||||
vue: 3.5.32(typescript@5.9.3)
|
||||
optionalDependencies:
|
||||
vite: 8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)
|
||||
transitivePeerDependencies:
|
||||
- typescript
|
||||
|
||||
'@vue-macros/devtools@3.1.2(typescript@5.9.3)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))':
|
||||
dependencies:
|
||||
sirv: 3.0.2
|
||||
@@ -25379,6 +25492,21 @@ snapshots:
|
||||
optionalDependencies:
|
||||
magicast: 0.5.2
|
||||
|
||||
c12@4.0.0-beta.4(chokidar@5.0.0)(dotenv@17.4.2)(giget@2.0.0)(jiti@2.6.1)(magicast@0.5.2):
|
||||
dependencies:
|
||||
confbox: 0.2.4
|
||||
defu: 6.1.7
|
||||
exsolve: 1.0.8
|
||||
pathe: 2.0.3
|
||||
pkg-types: 2.3.0
|
||||
rc9: 3.0.1
|
||||
optionalDependencies:
|
||||
chokidar: 5.0.0
|
||||
dotenv: 17.4.2
|
||||
giget: 2.0.0
|
||||
jiti: 2.6.1
|
||||
magicast: 0.5.2
|
||||
|
||||
cac@6.7.14: {}
|
||||
|
||||
cac@7.0.0: {}
|
||||
@@ -25459,6 +25587,8 @@ snapshots:
|
||||
|
||||
character-entities@2.0.2: {}
|
||||
|
||||
chess.js@1.4.0: {}
|
||||
|
||||
chevrotain@10.5.0:
|
||||
dependencies:
|
||||
'@chevrotain/cst-dts-gen': 10.5.0
|
||||
@@ -25672,6 +25802,8 @@ snapshots:
|
||||
|
||||
confbox@0.2.2: {}
|
||||
|
||||
confbox@0.2.4: {}
|
||||
|
||||
config-chain@1.1.13:
|
||||
dependencies:
|
||||
ini: 1.3.8
|
||||
@@ -25968,6 +26100,8 @@ snapshots:
|
||||
|
||||
date-fns@4.1.0: {}
|
||||
|
||||
dayjs@1.11.20: {}
|
||||
|
||||
de-indent@1.0.2: {}
|
||||
|
||||
debounce@1.2.1: {}
|
||||
@@ -26305,7 +26439,7 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
electron-vite@5.0.0(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)):
|
||||
electron-vite@5.0.0(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)):
|
||||
dependencies:
|
||||
'@babel/core': 7.29.0
|
||||
'@babel/plugin-transform-arrow-functions': 7.27.1(@babel/core@7.29.0)
|
||||
@@ -26313,7 +26447,7 @@ snapshots:
|
||||
esbuild: 0.25.12
|
||||
magic-string: 0.30.21
|
||||
picocolors: 1.1.1
|
||||
vite: 8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)
|
||||
vite: 8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
@@ -27110,10 +27244,16 @@ snapshots:
|
||||
|
||||
fast-string-truncated-width@1.2.1: {}
|
||||
|
||||
fast-string-truncated-width@3.0.3: {}
|
||||
|
||||
fast-string-width@1.1.0:
|
||||
dependencies:
|
||||
fast-string-truncated-width: 1.2.1
|
||||
|
||||
fast-string-width@3.0.2:
|
||||
dependencies:
|
||||
fast-string-truncated-width: 3.0.3
|
||||
|
||||
fast-uri@3.1.0: {}
|
||||
|
||||
fast-wrap-ansi@0.1.6:
|
||||
@@ -30682,6 +30822,11 @@ snapshots:
|
||||
defu: 6.1.7
|
||||
destr: 2.0.5
|
||||
|
||||
rc9@3.0.1:
|
||||
dependencies:
|
||||
defu: 6.1.7
|
||||
destr: 2.0.5
|
||||
|
||||
rc@1.2.8:
|
||||
dependencies:
|
||||
deep-extend: 0.6.0
|
||||
@@ -31591,6 +31736,8 @@ snapshots:
|
||||
|
||||
std-env@4.1.0: {}
|
||||
|
||||
stockfish@18.0.7: {}
|
||||
|
||||
store2@2.14.4: {}
|
||||
|
||||
string-width@4.2.3:
|
||||
@@ -32162,8 +32309,7 @@ snapshots:
|
||||
|
||||
undici-types@7.16.0: {}
|
||||
|
||||
undici-types@7.19.2:
|
||||
optional: true
|
||||
undici-types@7.19.2: {}
|
||||
|
||||
undici@6.24.1: {}
|
||||
|
||||
@@ -32273,6 +32419,11 @@ snapshots:
|
||||
'@unocss/preset-mini': 66.6.8
|
||||
unocss: 66.6.8(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))
|
||||
|
||||
unocss-preset-scrollbar@4.0.0(unocss@66.6.8(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))):
|
||||
dependencies:
|
||||
'@unocss/preset-mini': 66.6.8
|
||||
unocss: 66.6.8(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))
|
||||
|
||||
unocss@66.6.8(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(vite@6.4.2(@types/node@25.6.0)(jiti@2.6.1)(less@4.6.4)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)):
|
||||
dependencies:
|
||||
'@unocss/cli': 66.6.8
|
||||
@@ -32321,6 +32472,30 @@ snapshots:
|
||||
- '@emnapi/runtime'
|
||||
- vite
|
||||
|
||||
unocss@66.6.8(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)):
|
||||
dependencies:
|
||||
'@unocss/cli': 66.6.8
|
||||
'@unocss/core': 66.6.8
|
||||
'@unocss/preset-attributify': 66.6.8
|
||||
'@unocss/preset-icons': 66.6.8
|
||||
'@unocss/preset-mini': 66.6.8
|
||||
'@unocss/preset-tagify': 66.6.8
|
||||
'@unocss/preset-typography': 66.6.8
|
||||
'@unocss/preset-uno': 66.6.8
|
||||
'@unocss/preset-web-fonts': 66.6.8
|
||||
'@unocss/preset-wind': 66.6.8
|
||||
'@unocss/preset-wind3': 66.6.8
|
||||
'@unocss/preset-wind4': 66.6.8
|
||||
'@unocss/transformer-attributify-jsx': 66.6.8(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)
|
||||
'@unocss/transformer-compile-class': 66.6.8
|
||||
'@unocss/transformer-directives': 66.6.8
|
||||
'@unocss/transformer-variant-group': 66.6.8
|
||||
'@unocss/vite': 66.6.8(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))
|
||||
transitivePeerDependencies:
|
||||
- '@emnapi/core'
|
||||
- '@emnapi/runtime'
|
||||
- vite
|
||||
|
||||
unpack-string@0.0.2: {}
|
||||
|
||||
unpipe@1.0.0: {}
|
||||
@@ -32333,14 +32508,6 @@ snapshots:
|
||||
unplugin: 2.3.11
|
||||
vite: 8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)
|
||||
|
||||
unplugin-combine@2.3.0(esbuild@0.27.2)(rolldown@1.0.0-rc.16)(rollup@4.60.1)(unplugin@2.3.11)(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)):
|
||||
optionalDependencies:
|
||||
esbuild: 0.27.2
|
||||
rolldown: 1.0.0-rc.16
|
||||
rollup: 4.60.1
|
||||
unplugin: 2.3.11
|
||||
vite: 8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)
|
||||
|
||||
unplugin-combine@2.3.0(esbuild@0.27.2)(rolldown@1.0.0-rc.16)(rollup@4.60.1)(unplugin@2.3.11)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)):
|
||||
optionalDependencies:
|
||||
esbuild: 0.27.2
|
||||
@@ -32375,19 +32542,6 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
unplugin-info@1.3.2(esbuild@0.27.2)(rollup@4.60.1)(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)):
|
||||
dependencies:
|
||||
ci-info: 4.4.0
|
||||
git-url-parse: 16.1.0
|
||||
simple-git: 3.36.0
|
||||
unplugin: 2.3.11
|
||||
optionalDependencies:
|
||||
esbuild: 0.27.2
|
||||
rollup: 4.60.1
|
||||
vite: 8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
unplugin-info@1.3.2(esbuild@0.27.2)(rollup@4.60.1)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)):
|
||||
dependencies:
|
||||
ci-info: 4.4.0
|
||||
@@ -32497,17 +32651,6 @@ snapshots:
|
||||
rollup: 4.60.1
|
||||
vite: 6.4.2(@types/node@25.6.0)(jiti@2.6.1)(less@4.6.4)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)
|
||||
|
||||
unplugin-yaml@4.1.0(esbuild@0.27.2)(rolldown@1.0.0-rc.16)(rollup@4.60.1)(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)):
|
||||
dependencies:
|
||||
'@rollup/pluginutils': 5.3.0(rollup@4.60.1)
|
||||
unplugin: 3.0.0
|
||||
yaml: 2.8.3
|
||||
optionalDependencies:
|
||||
esbuild: 0.27.2
|
||||
rolldown: 1.0.0-rc.16
|
||||
rollup: 4.60.1
|
||||
vite: 8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)
|
||||
|
||||
unplugin@2.3.11:
|
||||
dependencies:
|
||||
'@jridgewell/remapping': 2.3.5
|
||||
@@ -32670,6 +32813,37 @@ snapshots:
|
||||
'@types/unist': 3.0.3
|
||||
vfile-message: 4.0.3
|
||||
|
||||
vieval@0.0.1(@types/node@25.6.0)(chokidar@5.0.0)(dotenv@17.4.2)(esbuild@0.27.2)(giget@2.0.0)(jiti@2.6.1)(less@4.6.4)(magicast@0.5.2)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3):
|
||||
dependencies:
|
||||
'@moeru/std': 0.1.0-beta.17
|
||||
'@pnpm/find-workspace-dir': 1000.1.5
|
||||
'@xsai-ext/providers': 0.5.0-beta.2
|
||||
'@xsai/generate-text': 0.5.0-beta.2(patch_hash=306bfb723913596b334140f0d6fa48063f336e3b44024efc1d72bf60d54b15e6)
|
||||
c12: 4.0.0-beta.4(chokidar@5.0.0)(dotenv@17.4.2)(giget@2.0.0)(jiti@2.6.1)(magicast@0.5.2)
|
||||
es-toolkit: 1.43.0
|
||||
fast-string-width: 3.0.2
|
||||
meow: 14.1.0
|
||||
tinyglobby: 0.2.16
|
||||
tinyrainbow: 3.1.0
|
||||
vite: 8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)
|
||||
transitivePeerDependencies:
|
||||
- '@types/node'
|
||||
- '@vitejs/devtools'
|
||||
- chokidar
|
||||
- dotenv
|
||||
- esbuild
|
||||
- giget
|
||||
- jiti
|
||||
- less
|
||||
- magicast
|
||||
- sass
|
||||
- sass-embedded
|
||||
- stylus
|
||||
- sugarss
|
||||
- terser
|
||||
- tsx
|
||||
- yaml
|
||||
|
||||
vite-bundle-visualizer@1.2.1(rolldown@1.0.0-rc.16)(rollup@2.80.0):
|
||||
dependencies:
|
||||
cac: 6.7.14
|
||||
@@ -32692,22 +32866,12 @@ snapshots:
|
||||
- rollup
|
||||
- supports-color
|
||||
|
||||
vite-dev-rpc@1.1.0(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)):
|
||||
dependencies:
|
||||
birpc: 2.9.0
|
||||
vite: 8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)
|
||||
vite-hot-client: 2.1.0(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))
|
||||
|
||||
vite-dev-rpc@1.1.0(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)):
|
||||
dependencies:
|
||||
birpc: 2.9.0
|
||||
vite: 8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)
|
||||
vite-hot-client: 2.1.0(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))
|
||||
|
||||
vite-hot-client@2.1.0(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)):
|
||||
dependencies:
|
||||
vite: 8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)
|
||||
|
||||
vite-hot-client@2.1.0(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)):
|
||||
dependencies:
|
||||
vite: 8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)
|
||||
@@ -32753,21 +32917,6 @@ snapshots:
|
||||
- tsx
|
||||
- yaml
|
||||
|
||||
vite-plugin-inspect@11.3.3(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)):
|
||||
dependencies:
|
||||
ansis: 4.2.0
|
||||
debug: 4.4.3(supports-color@10.2.2)
|
||||
error-stack-parser-es: 1.0.5
|
||||
ohash: 2.0.11
|
||||
open: 10.2.0
|
||||
perfect-debounce: 2.1.0
|
||||
sirv: 3.0.2
|
||||
unplugin-utils: 0.3.1
|
||||
vite: 8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)
|
||||
vite-dev-rpc: 1.1.0(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
vite-plugin-inspect@11.3.3(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)):
|
||||
dependencies:
|
||||
ansis: 4.2.0
|
||||
@@ -32799,13 +32948,6 @@ snapshots:
|
||||
- typescript
|
||||
- ws
|
||||
|
||||
vite-plugin-mkcert@2.0.0(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)):
|
||||
dependencies:
|
||||
debug: 4.4.3(supports-color@10.2.2)
|
||||
supports-color: 10.2.2
|
||||
undici: 8.1.0
|
||||
vite: 8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)
|
||||
|
||||
vite-plugin-mkcert@2.0.0(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)):
|
||||
dependencies:
|
||||
debug: 4.4.3(supports-color@10.2.2)
|
||||
@@ -32824,20 +32966,6 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
vite-plugin-vue-devtools@8.1.1(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vue@3.5.32(typescript@5.9.3)):
|
||||
dependencies:
|
||||
'@vue/devtools-core': 8.1.1(vue@3.5.32(typescript@5.9.3))
|
||||
'@vue/devtools-kit': 8.1.1
|
||||
'@vue/devtools-shared': 8.1.1
|
||||
sirv: 3.0.2
|
||||
vite: 8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)
|
||||
vite-plugin-inspect: 11.3.3(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))
|
||||
vite-plugin-vue-inspector: 5.3.2(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))
|
||||
transitivePeerDependencies:
|
||||
- '@nuxt/kit'
|
||||
- supports-color
|
||||
- vue
|
||||
|
||||
vite-plugin-vue-devtools@8.1.1(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vue@3.5.32(typescript@5.9.3)):
|
||||
dependencies:
|
||||
'@vue/devtools-core': 8.1.1(vue@3.5.32(typescript@5.9.3))
|
||||
@@ -32852,21 +32980,6 @@ snapshots:
|
||||
- supports-color
|
||||
- vue
|
||||
|
||||
vite-plugin-vue-inspector@5.3.2(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)):
|
||||
dependencies:
|
||||
'@babel/core': 7.29.0
|
||||
'@babel/plugin-proposal-decorators': 7.28.0(@babel/core@7.29.0)
|
||||
'@babel/plugin-syntax-import-attributes': 7.28.6(@babel/core@7.29.0)
|
||||
'@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.29.0)
|
||||
'@babel/plugin-transform-typescript': 7.28.5(@babel/core@7.29.0)
|
||||
'@vue/babel-plugin-jsx': 1.5.0(@babel/core@7.29.0)
|
||||
'@vue/compiler-dom': 3.5.32
|
||||
kolorist: 1.8.0
|
||||
magic-string: 0.30.21
|
||||
vite: 8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
vite-plugin-vue-inspector@5.3.2(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)):
|
||||
dependencies:
|
||||
'@babel/core': 7.29.0
|
||||
@@ -32882,16 +32995,6 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
vite-plugin-vue-layouts@0.11.0(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vue-router@5.0.4(@vue/compiler-sfc@3.5.32)(pinia@3.0.4(typescript@5.9.3)(vue@3.5.32(typescript@5.9.3)))(vue@3.5.32(typescript@5.9.3)))(vue@3.5.32(typescript@5.9.3)):
|
||||
dependencies:
|
||||
debug: 4.4.3(supports-color@10.2.2)
|
||||
fast-glob: 3.3.3
|
||||
vite: 8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)
|
||||
vue: 3.5.32(typescript@5.9.3)
|
||||
vue-router: 5.0.4(@vue/compiler-sfc@3.5.32)(pinia@3.0.4(typescript@5.9.3)(vue@3.5.32(typescript@5.9.3)))(vue@3.5.32(typescript@5.9.3))
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
vite-plugin-vue-layouts@0.11.0(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vue-router@5.0.4(@vue/compiler-sfc@3.5.32)(pinia@3.0.4(typescript@5.9.3)(vue@3.5.32(typescript@5.9.3)))(vue@3.5.32(typescript@5.9.3)))(vue@3.5.32(typescript@5.9.3)):
|
||||
dependencies:
|
||||
debug: 4.4.3(supports-color@10.2.2)
|
||||
@@ -33150,54 +33253,6 @@ snapshots:
|
||||
- vue-tsc
|
||||
- webpack
|
||||
|
||||
vue-macros@3.1.2(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(@vueuse/core@14.2.1(vue@3.5.32(typescript@5.9.3)))(esbuild@0.27.2)(rolldown@1.0.0-rc.16)(rollup@4.60.1)(typescript@5.9.3)(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vue-tsc@3.2.6(typescript@5.9.3))(vue@3.5.32(typescript@5.9.3)):
|
||||
dependencies:
|
||||
'@vue-macros/better-define': 3.1.2(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(vue@3.5.32(typescript@5.9.3))
|
||||
'@vue-macros/boolean-prop': 3.1.2(vue@3.5.32(typescript@5.9.3))
|
||||
'@vue-macros/chain-call': 3.1.2(vue@3.5.32(typescript@5.9.3))
|
||||
'@vue-macros/common': 3.1.2(vue@3.5.32(typescript@5.9.3))
|
||||
'@vue-macros/config': 3.1.2(vue@3.5.32(typescript@5.9.3))
|
||||
'@vue-macros/define-emit': 3.1.2(vue@3.5.32(typescript@5.9.3))
|
||||
'@vue-macros/define-models': 3.1.2(@vueuse/core@14.2.1(vue@3.5.32(typescript@5.9.3)))(vue@3.5.32(typescript@5.9.3))
|
||||
'@vue-macros/define-prop': 3.1.2(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(vue@3.5.32(typescript@5.9.3))
|
||||
'@vue-macros/define-props': 3.1.2(@vue-macros/reactivity-transform@3.1.2(vue@3.5.32(typescript@5.9.3)))(vue@3.5.32(typescript@5.9.3))
|
||||
'@vue-macros/define-props-refs': 3.1.2(vue@3.5.32(typescript@5.9.3))
|
||||
'@vue-macros/define-render': 3.1.2(vue@3.5.32(typescript@5.9.3))
|
||||
'@vue-macros/define-slots': 3.1.2(vue@3.5.32(typescript@5.9.3))
|
||||
'@vue-macros/define-stylex': 3.1.2(vue@3.5.32(typescript@5.9.3))
|
||||
'@vue-macros/devtools': 3.1.2(typescript@5.9.3)(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))
|
||||
'@vue-macros/export-expose': 3.1.2(vue@3.5.32(typescript@5.9.3))
|
||||
'@vue-macros/export-props': 3.1.2(vue@3.5.32(typescript@5.9.3))
|
||||
'@vue-macros/export-render': 3.1.2(vue@3.5.32(typescript@5.9.3))
|
||||
'@vue-macros/hoist-static': 3.1.2(vue@3.5.32(typescript@5.9.3))
|
||||
'@vue-macros/jsx-directive': 3.1.2(typescript@5.9.3)
|
||||
'@vue-macros/named-template': 3.1.2(vue@3.5.32(typescript@5.9.3))
|
||||
'@vue-macros/reactivity-transform': 3.1.2(vue@3.5.32(typescript@5.9.3))
|
||||
'@vue-macros/script-lang': 3.1.2(vue@3.5.32(typescript@5.9.3))
|
||||
'@vue-macros/setup-block': 3.1.2(vue@3.5.32(typescript@5.9.3))
|
||||
'@vue-macros/setup-component': 3.1.2(vue@3.5.32(typescript@5.9.3))
|
||||
'@vue-macros/setup-sfc': 3.1.2(vue@3.5.32(typescript@5.9.3))
|
||||
'@vue-macros/short-bind': 3.1.2(vue@3.5.32(typescript@5.9.3))
|
||||
'@vue-macros/short-emits': 3.1.2(vue@3.5.32(typescript@5.9.3))
|
||||
'@vue-macros/short-vmodel': 3.1.2(vue@3.5.32(typescript@5.9.3))
|
||||
'@vue-macros/volar': 3.1.2(typescript@5.9.3)(vue-tsc@3.2.6(typescript@5.9.3))(vue@3.5.32(typescript@5.9.3))
|
||||
unplugin: 2.3.11
|
||||
unplugin-combine: 2.3.0(esbuild@0.27.2)(rolldown@1.0.0-rc.16)(rollup@4.60.1)(unplugin@2.3.11)(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))
|
||||
unplugin-vue-define-options: 3.1.2(vue@3.5.32(typescript@5.9.3))
|
||||
vue: 3.5.32(typescript@5.9.3)
|
||||
transitivePeerDependencies:
|
||||
- '@emnapi/core'
|
||||
- '@emnapi/runtime'
|
||||
- '@rspack/core'
|
||||
- '@vueuse/core'
|
||||
- esbuild
|
||||
- rolldown
|
||||
- rollup
|
||||
- typescript
|
||||
- vite
|
||||
- vue-tsc
|
||||
- webpack
|
||||
|
||||
vue-macros@3.1.2(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(@vueuse/core@14.2.1(vue@3.5.32(typescript@5.9.3)))(esbuild@0.27.2)(rolldown@1.0.0-rc.16)(rollup@4.60.1)(typescript@5.9.3)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vue-tsc@3.2.6(typescript@5.9.3))(vue@3.5.32(typescript@5.9.3)):
|
||||
dependencies:
|
||||
'@vue-macros/better-define': 3.1.2(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(vue@3.5.32(typescript@5.9.3))
|
||||
@@ -33377,6 +33432,8 @@ snapshots:
|
||||
|
||||
whatwg-mimetype@4.0.0: {}
|
||||
|
||||
whatwg-mimetype@5.0.0: {}
|
||||
|
||||
whatwg-url@15.1.0:
|
||||
dependencies:
|
||||
tr46: 6.0.0
|
||||
|
||||
@@ -28,6 +28,7 @@ patchedDependencies:
|
||||
mineflayer@4.33.0: patches/mineflayer@4.33.0.patch
|
||||
pixi-live2d-display: patches/pixi-live2d-display.patch
|
||||
catalog:
|
||||
'@ax-llm/ax': ^19.0.43
|
||||
'@better-auth/oauth-provider': 1.5.6
|
||||
'@capacitor/android': ^8.3.1
|
||||
'@capacitor/barcode-scanner': ^3.0.2
|
||||
@@ -67,6 +68,7 @@ catalog:
|
||||
'@types/hast': ^3.0.4
|
||||
'@types/splitpanes': ^2.2.6
|
||||
'@types/unist': ^3.0.3
|
||||
'@types/whatwg-mimetype': ^5.0.0
|
||||
'@types/ws': ^8.18.1
|
||||
'@vueuse/core': 14.1.0
|
||||
'@xsai-ext/providers': 0.5.0-beta.2
|
||||
@@ -87,6 +89,7 @@ catalog:
|
||||
builder-util-runtime: ^9.5.1
|
||||
cac: ^7.0.0
|
||||
capacitor-native-settings: ^8.1.0
|
||||
chess.js: ^1.4.0
|
||||
crossws: ^0.4.5
|
||||
d3: 7.9.0
|
||||
date-fns: ^4.1.0
|
||||
@@ -115,8 +118,10 @@ catalog:
|
||||
oxc-minify: ^0.126.0
|
||||
pinia: ^3.0.4
|
||||
posthog-js: 1.306.1
|
||||
reka-ui: ^2.9.2
|
||||
splitpanes: ^4.0.4
|
||||
std-env: ^4.1.0
|
||||
stockfish: ^18.0.7
|
||||
superjson: ^2.2.6
|
||||
tsdown: ^0.21.9
|
||||
tsx: ^4.21.0
|
||||
@@ -125,6 +130,7 @@ catalog:
|
||||
unstorage: ^1.17.5
|
||||
uqr: ^0.1.3
|
||||
valibot: 1.2.0
|
||||
vieval: ^0.0.1
|
||||
vite: ^8.0.8
|
||||
vite-plugin-inspect: 12.0.0-beta.1
|
||||
vite-plugin-mkcert: ^2.0.0
|
||||
@@ -132,6 +138,7 @@ catalog:
|
||||
vue-router: ^5.0.4
|
||||
vue-sonner: 2.0.9
|
||||
web-haptics: ^0.0.6
|
||||
whatwg-mimetype: ^5.0.0
|
||||
ws: ^8.20.0
|
||||
xsschema: 0.5.0-beta.2
|
||||
yaml: ^2.8.3
|
||||
@@ -173,6 +180,7 @@ onlyBuiltDependencies:
|
||||
- protobufjs
|
||||
- sharp
|
||||
- spawn-sync
|
||||
- stockfish
|
||||
- utf-8-validate
|
||||
- vue-demi
|
||||
packageExtensions:
|
||||
|
||||
Reference in New Issue
Block a user