feat: add DSH IDE open-file references
This commit is contained in:
@@ -0,0 +1,27 @@
|
||||
# DSH Cordis plugin
|
||||
|
||||
This package is both a normal dual-face Cordis plugin and a DSH profile bundle.
|
||||
|
||||
- Host: reads and validates companion snapshots, then serves `GET /coop-with-vs/open-files` to loopback clients.
|
||||
- Client: registers the `ide-file` source in the existing `@` input-trigger pipeline.
|
||||
- Pick result: a native composer reference chip whose model projection is `<ide-open-file>{"path":"..."}</ide-open-file>`.
|
||||
|
||||
No source content crosses the companion protocol or candidate endpoint.
|
||||
|
||||
## Build and test
|
||||
|
||||
```powershell
|
||||
pnpm install
|
||||
pnpm test
|
||||
pnpm build
|
||||
```
|
||||
|
||||
`lib/client.js` is wrapped in the `window.__ModuleLoader__.load` format expected by DSH Client Modules.
|
||||
|
||||
## Install
|
||||
|
||||
```powershell
|
||||
dsh plugin --profile web add F:\dsh-plugin-coop-with-vs\dsh-plugin
|
||||
```
|
||||
|
||||
The profile manager recognizes `dsh.bundle.patch` and appends this package to the Web profile's bundle list. `cordis.patch.yml` inserts one shared `coop-with-vs` row. Restart DSH after first installation.
|
||||
@@ -0,0 +1,4 @@
|
||||
# Profile bundle patch. The package's dsh.client manifest contributes its browser half.
|
||||
- insert:
|
||||
- id: coop-with-vs
|
||||
name: dsh-plugin-coop-with-vs
|
||||
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"name": "@prophet/dsh-plugin-coop-with-vs",
|
||||
"version": "0.1.0",
|
||||
"description": "Use files open in VS Code and Visual Studio as DSH @ references",
|
||||
"type": "module",
|
||||
"main": "lib/index.js",
|
||||
"types": "lib/types/index.d.ts",
|
||||
"repository": { "type": "git", "url": "https://www.prophetk.icu/Prophet/dsh-plugin-coop-with-vs.git", "directory": "dsh-plugin" },
|
||||
"exports": {
|
||||
".": { "types": "./lib/types/index.d.ts", "default": "./lib/index.js" },
|
||||
"./client": { "types": "./lib/types/client.d.ts", "default": "./lib/client.js" },
|
||||
"./cordis.patch.yml": "./cordis.patch.yml",
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"files": ["lib/index.js", "lib/client.js", "lib/types/**/*.d.ts", "cordis.patch.yml", "README.md"],
|
||||
"dsh": {
|
||||
"bundle": { "patch": "./cordis.patch.yml" },
|
||||
"client": { "inject": ["@deepseek-ai/dsh-client-ui-input-trigger"], "platform": "web" }
|
||||
},
|
||||
"scripts": {
|
||||
"build": "node scripts/build.mjs && tsc -p tsconfig.build.json",
|
||||
"test": "vitest run",
|
||||
"watch": "node scripts/build.mjs --watch"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@deepseek-ai/cordis": "^4.0.1",
|
||||
"@deepseek-ai/dsh-client-ui-input-trigger": ">=0.1.0-rc.5 <0.2.0",
|
||||
"@deepseek-ai/dsh-host-webserver": ">=0.1.0-rc.5 <0.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@deepseek-ai/cordis": "4.0.1",
|
||||
"@deepseek-ai/dsh-client-ui-input-trigger": "0.1.0-rc.6",
|
||||
"@deepseek-ai/dsh-host-webserver": "0.1.0-rc.6",
|
||||
"@types/node": "^22.13.4",
|
||||
"esbuild": "^0.25.0",
|
||||
"typescript": "^5.7.3",
|
||||
"vitest": "^3.0.5"
|
||||
},
|
||||
"license": "MIT"
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import { build } from 'esbuild'
|
||||
import { mkdir, rm, writeFile } from 'node:fs/promises'
|
||||
|
||||
const watch = process.argv.includes('--watch')
|
||||
const packageId = '@prophet/dsh-plugin-coop-with-vs'
|
||||
|
||||
await rm('lib', { recursive: true, force: true })
|
||||
await mkdir('lib', { recursive: true })
|
||||
|
||||
const common = {
|
||||
bundle: true,
|
||||
sourcemap: true,
|
||||
target: 'es2022',
|
||||
logLevel: 'info',
|
||||
}
|
||||
|
||||
await build({
|
||||
...common,
|
||||
entryPoints: ['src/index.ts'],
|
||||
outfile: 'lib/index.js',
|
||||
platform: 'node',
|
||||
format: 'esm',
|
||||
packages: 'external',
|
||||
})
|
||||
|
||||
const client = await build({
|
||||
...common,
|
||||
entryPoints: ['src/client.ts'],
|
||||
platform: 'browser',
|
||||
format: 'cjs',
|
||||
packages: 'external',
|
||||
write: false,
|
||||
})
|
||||
const body = client.outputFiles[0].text
|
||||
const wrapped = `window.__ModuleLoader__.load({\n id: ${JSON.stringify(packageId)},\n factory: (require) => {\n var module = { exports: {} };\n var exports = module.exports;\n${body}\n return module.exports;\n },\n});\n`
|
||||
await writeFile('lib/client.js', wrapped, 'utf8')
|
||||
|
||||
if (watch) {
|
||||
console.error('Watch mode performs one deterministic rebuild; rerun via your file watcher.')
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
import { basename, isAbsolute } from 'node:path'
|
||||
import type { IdeSnapshot, OpenFileItem, OpenFileResponse, SnapshotDocument } from './protocol.js'
|
||||
|
||||
const MAX_TEXT = 32_768
|
||||
const MAX_DOCUMENTS = 10_000
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
||||
}
|
||||
|
||||
function hasOnlyKeys(value: Record<string, unknown>, allowed: readonly string[]): boolean {
|
||||
const keys = new Set(allowed)
|
||||
return Object.keys(value).every((key) => keys.has(key))
|
||||
}
|
||||
|
||||
function boundedString(value: unknown, max = MAX_TEXT): value is string {
|
||||
return typeof value === 'string' && value.length > 0 && value.length <= max && !value.includes('\0')
|
||||
}
|
||||
|
||||
function parseDocument(value: unknown): SnapshotDocument | undefined {
|
||||
if (!isRecord(value) || !hasOnlyKeys(value, ['path', 'label', 'dirty']) || !boundedString(value.path) || !isAbsolute(value.path)) return undefined
|
||||
if (!boundedString(value.label, 1024) || typeof value.dirty !== 'boolean') return undefined
|
||||
return { path: value.path, label: value.label, dirty: value.dirty }
|
||||
}
|
||||
|
||||
export function parseSnapshot(value: unknown, now = Date.now(), maxAgeMs = 60_000): IdeSnapshot | undefined {
|
||||
if (!isRecord(value) || !hasOnlyKeys(value, ['version', 'instanceId', 'ide', 'processId', 'workspace', 'updatedAt', 'documents']) || value.version !== 1) return undefined
|
||||
if (!boundedString(value.instanceId, 128)) return undefined
|
||||
if (value.ide !== 'vscode' && value.ide !== 'visualstudio') return undefined
|
||||
if (!Number.isSafeInteger(value.processId) || (value.processId as number) <= 0) return undefined
|
||||
if (value.workspace !== undefined && !boundedString(value.workspace)) return undefined
|
||||
if (!boundedString(value.updatedAt, 128) || !Array.isArray(value.documents) || value.documents.length > MAX_DOCUMENTS) return undefined
|
||||
const updated = Date.parse(value.updatedAt)
|
||||
if (!Number.isFinite(updated) || now - updated > maxAgeMs || updated - now > 300_000) return undefined
|
||||
|
||||
const documents: SnapshotDocument[] = []
|
||||
const seen = new Set<string>()
|
||||
for (const raw of value.documents) {
|
||||
const document = parseDocument(raw)
|
||||
if (!document) continue
|
||||
const key = document.path.toLocaleLowerCase('en-US')
|
||||
if (seen.has(key)) continue
|
||||
seen.add(key)
|
||||
documents.push(document)
|
||||
}
|
||||
|
||||
return {
|
||||
version: 1,
|
||||
instanceId: value.instanceId,
|
||||
ide: value.ide,
|
||||
processId: value.processId as number,
|
||||
...(value.workspace === undefined ? {} : { workspace: value.workspace }),
|
||||
updatedAt: value.updatedAt,
|
||||
documents,
|
||||
}
|
||||
}
|
||||
|
||||
function score(item: OpenFileItem, query: string): number {
|
||||
if (!query) return 0
|
||||
const q = query.toLocaleLowerCase('en-US')
|
||||
const label = item.label.toLocaleLowerCase('en-US')
|
||||
const path = item.path.toLocaleLowerCase('en-US')
|
||||
if (label === q) return 0
|
||||
if (label.startsWith(q)) return 1
|
||||
if (label.includes(q)) return 2
|
||||
if (path.includes(q)) return 3
|
||||
return 10
|
||||
}
|
||||
|
||||
export function combineSnapshots(snapshots: readonly IdeSnapshot[], query = '', limit = 100): OpenFileResponse {
|
||||
const normalizedQuery = query.trim().slice(0, 256)
|
||||
const byPath = new Map<string, OpenFileItem>()
|
||||
for (const snapshot of snapshots) {
|
||||
for (const document of snapshot.documents) {
|
||||
const key = document.path.toLocaleLowerCase('en-US')
|
||||
const current = byPath.get(key)
|
||||
const next: OpenFileItem = {
|
||||
...document,
|
||||
ide: snapshot.ide,
|
||||
instanceId: snapshot.instanceId,
|
||||
...(snapshot.workspace === undefined ? {} : { workspace: snapshot.workspace }),
|
||||
instanceOpenCount: snapshot.documents.length,
|
||||
}
|
||||
if (!current || (!current.dirty && next.dirty)) byPath.set(key, next)
|
||||
}
|
||||
}
|
||||
|
||||
const matching = [...byPath.values()].filter((item) => score(item, normalizedQuery) < 10)
|
||||
matching.sort((a, b) => score(a, normalizedQuery) - score(b, normalizedQuery)
|
||||
|| a.label.localeCompare(b.label, undefined, { sensitivity: 'base' })
|
||||
|| a.path.localeCompare(b.path, undefined, { sensitivity: 'base' }))
|
||||
const safeLimit = Math.max(1, Math.min(Number.isFinite(limit) ? Math.floor(limit) : 100, 200))
|
||||
return { total: matching.length, instances: snapshots.length, items: matching.slice(0, safeLimit) }
|
||||
}
|
||||
|
||||
export function displayLabel(path: string): string {
|
||||
return basename(path) || path
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
import type { Context } from '@deepseek-ai/cordis'
|
||||
import type {
|
||||
InputTriggerCandidate,
|
||||
InputTriggerPick,
|
||||
InputTriggerSource,
|
||||
ReferenceInsert,
|
||||
} from '@deepseek-ai/dsh-client-ui-input-trigger/client'
|
||||
import type { OpenFileItem, OpenFileResponse } from './protocol.js'
|
||||
|
||||
interface IdeCandidate extends InputTriggerCandidate {
|
||||
path: string
|
||||
label: string
|
||||
}
|
||||
|
||||
function isOpenFileItem(value: unknown): value is OpenFileItem {
|
||||
if (typeof value !== 'object' || value === null) return false
|
||||
const item = value as Partial<OpenFileItem>
|
||||
return typeof item.path === 'string' && item.path.length > 0
|
||||
&& typeof item.label === 'string' && item.label.length > 0
|
||||
&& (item.ide === 'vscode' || item.ide === 'visualstudio')
|
||||
&& typeof item.instanceOpenCount === 'number'
|
||||
&& typeof item.dirty === 'boolean'
|
||||
}
|
||||
|
||||
function ideName(ide: OpenFileItem['ide']): string {
|
||||
return ide === 'vscode' ? 'VS Code' : 'Visual Studio'
|
||||
}
|
||||
|
||||
export function candidateFromItem(item: OpenFileItem): IdeCandidate {
|
||||
const details = [ideName(item.ide), item.workspace, `${item.instanceOpenCount} open`, item.dirty ? 'unsaved changes' : undefined]
|
||||
.filter((value): value is string => Boolean(value))
|
||||
return { name: item.label, description: details.join(' 璺?'), hint: item.path, path: item.path, label: item.label }
|
||||
}
|
||||
|
||||
export function serializeIdeReference(path: string): string {
|
||||
return `<ide-open-file>${JSON.stringify({ path })}</ide-open-file>`
|
||||
}
|
||||
|
||||
export function referenceFromPick(pick: InputTriggerPick): ReferenceInsert | undefined {
|
||||
const candidate = pick.candidate as Partial<IdeCandidate>
|
||||
if (typeof candidate.path !== 'string' || typeof candidate.label !== 'string') return undefined
|
||||
return { source: 'ide-file', ref: candidate.path, label: candidate.label, clipboardText: candidate.path }
|
||||
}
|
||||
|
||||
export const inject = ['inputTriggers']
|
||||
|
||||
export function apply(ctx: Context): void {
|
||||
const source: InputTriggerSource = {
|
||||
trigger: '@',
|
||||
name: 'ide-file',
|
||||
order: 20,
|
||||
async candidates(_session, request) {
|
||||
const params = new URLSearchParams({ q: request.query, limit: '100' })
|
||||
const response = await fetch(`/coop-with-vs/open-files?${params}`, {
|
||||
method: 'GET',
|
||||
signal: request.signal,
|
||||
headers: { accept: 'application/json' },
|
||||
})
|
||||
if (!response.ok) return []
|
||||
const payload = await response.json() as Partial<OpenFileResponse>
|
||||
return Array.isArray(payload.items) ? payload.items.filter(isOpenFileItem).map(candidateFromItem) : []
|
||||
},
|
||||
onPick(pick) {
|
||||
const insert = referenceFromPick(pick)
|
||||
return insert ? { insert } : undefined
|
||||
},
|
||||
codec: {
|
||||
clipboardText: (path) => path,
|
||||
serialize: (path) => Promise.resolve(serializeIdeReference(path)),
|
||||
},
|
||||
}
|
||||
ctx.effect(() => ctx.inputTriggers.registerSource(source), 'coop-with-vs: IDE open files')
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
import { readdir, readFile } from 'node:fs/promises'
|
||||
import { join } from 'node:path'
|
||||
import type { Context } from '@deepseek-ai/cordis'
|
||||
import type { WebServer } from '@deepseek-ai/dsh-host-webserver'
|
||||
import { combineSnapshots, parseSnapshot } from './catalog.js'
|
||||
import type { IdeSnapshot } from './protocol.js'
|
||||
|
||||
const ROUTE = '/coop-with-vs/open-files'
|
||||
|
||||
interface HostContext extends Context {
|
||||
webServer: WebServer
|
||||
}
|
||||
|
||||
export function snapshotDirectory(localAppData = process.env.LOCALAPPDATA): string | undefined {
|
||||
return localAppData ? join(localAppData, 'Prophet', 'dsh-plugin-coop-with-vs', 'v1', 'instances') : undefined
|
||||
}
|
||||
|
||||
export function isLoopbackAddress(address: string | undefined): boolean {
|
||||
return address === '127.0.0.1' || address === '::1' || address === '::ffff:127.0.0.1'
|
||||
}
|
||||
|
||||
export async function loadSnapshots(directory: string | undefined, now = Date.now()): Promise<IdeSnapshot[]> {
|
||||
if (!directory) return []
|
||||
let names: string[]
|
||||
try {
|
||||
names = await readdir(directory)
|
||||
} catch (error) {
|
||||
if ((error as NodeJS.ErrnoException).code === 'ENOENT') return []
|
||||
throw error
|
||||
}
|
||||
|
||||
const snapshots: IdeSnapshot[] = []
|
||||
await Promise.all(names.filter((name) => /^(vscode|visualstudio)-.+\.json$/i.test(name)).map(async (name) => {
|
||||
try {
|
||||
const text = await readFile(join(directory, name), 'utf8')
|
||||
if (text.length > 8 * 1024 * 1024) return
|
||||
const snapshot = parseSnapshot(JSON.parse(text) as unknown, now)
|
||||
if (snapshot) snapshots.push(snapshot)
|
||||
} catch {
|
||||
// One partially written or stale instance must not hide healthy IDEs.
|
||||
}
|
||||
}))
|
||||
return snapshots
|
||||
}
|
||||
|
||||
export const inject = ['webServer']
|
||||
|
||||
export function apply(ctx: HostContext): void {
|
||||
const directory = snapshotDirectory()
|
||||
ctx.effect(() => ctx.webServer.register({
|
||||
kind: 'exact',
|
||||
path: ROUTE,
|
||||
async handler(req, res) {
|
||||
if (!isLoopbackAddress(req.socket.remoteAddress)) {
|
||||
res.writeHead(403, { 'content-type': 'application/json; charset=utf-8', 'cache-control': 'no-store' })
|
||||
res.end(JSON.stringify({ error: 'loopback-required' }))
|
||||
return
|
||||
}
|
||||
if (req.method !== 'GET') {
|
||||
res.writeHead(405, { allow: 'GET', 'content-type': 'application/json; charset=utf-8', 'cache-control': 'no-store' })
|
||||
res.end(JSON.stringify({ error: 'method-not-allowed' }))
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const url = new URL(req.url ?? ROUTE, 'http://127.0.0.1')
|
||||
const query = (url.searchParams.get('q') ?? '').slice(0, 256)
|
||||
const requestedLimit = Number(url.searchParams.get('limit') ?? '100')
|
||||
const snapshots = await loadSnapshots(directory)
|
||||
const result = combineSnapshots(snapshots, query, requestedLimit)
|
||||
res.writeHead(200, { 'content-type': 'application/json; charset=utf-8', 'cache-control': 'no-store' })
|
||||
res.end(JSON.stringify(result))
|
||||
} catch {
|
||||
res.writeHead(500, { 'content-type': 'application/json; charset=utf-8', 'cache-control': 'no-store' })
|
||||
res.end(JSON.stringify({ error: 'snapshot-read-failed' }))
|
||||
}
|
||||
},
|
||||
}))
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
export type IdeKind = 'vscode' | 'visualstudio'
|
||||
|
||||
export interface SnapshotDocument {
|
||||
path: string
|
||||
label: string
|
||||
dirty: boolean
|
||||
}
|
||||
|
||||
export interface IdeSnapshot {
|
||||
version: 1
|
||||
instanceId: string
|
||||
ide: IdeKind
|
||||
processId: number
|
||||
workspace?: string
|
||||
updatedAt: string
|
||||
documents: SnapshotDocument[]
|
||||
}
|
||||
|
||||
export interface OpenFileItem extends SnapshotDocument {
|
||||
ide: IdeKind
|
||||
instanceId: string
|
||||
workspace?: string
|
||||
instanceOpenCount: number
|
||||
}
|
||||
|
||||
export interface OpenFileResponse {
|
||||
total: number
|
||||
instances: number
|
||||
items: OpenFileItem[]
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { combineSnapshots, parseSnapshot } from '../src/catalog.js'
|
||||
|
||||
const now = Date.parse('2026-01-01T00:00:30.000Z')
|
||||
|
||||
function snapshot(path: string, overrides: Record<string, unknown> = {}) {
|
||||
return {
|
||||
version: 1,
|
||||
instanceId: 'vscode-a',
|
||||
ide: 'vscode',
|
||||
processId: 10,
|
||||
workspace: 'F:\\project',
|
||||
updatedAt: '2026-01-01T00:00:00.000Z',
|
||||
documents: [{ path, label: 'main.ts', dirty: false }],
|
||||
...overrides,
|
||||
}
|
||||
}
|
||||
|
||||
describe('parseSnapshot', () => {
|
||||
it('accepts valid absolute files', () => {
|
||||
expect(parseSnapshot(snapshot('F:\\project\\main.ts'), now)?.documents).toHaveLength(1)
|
||||
})
|
||||
|
||||
it('rejects stale and unknown versions', () => {
|
||||
expect(parseSnapshot(snapshot('F:\\project\\main.ts'), now + 61_000)).toBeUndefined()
|
||||
expect(parseSnapshot(snapshot('F:\\project\\main.ts', { version: 2 }), now)).toBeUndefined()
|
||||
})
|
||||
|
||||
it('rejects unknown v1 fields', () => {
|
||||
expect(parseSnapshot(snapshot('F:\\\\project\\\\main.ts', { extra: true }), now)).toBeUndefined()
|
||||
})
|
||||
|
||||
it('drops invalid and duplicate documents without dropping the instance', () => {
|
||||
const value = snapshot('F:\\project\\main.ts', {
|
||||
documents: [
|
||||
{ path: 'relative.ts', label: 'relative.ts', dirty: false },
|
||||
{ path: 'F:\\project\\main.ts', label: 'main.ts', dirty: false },
|
||||
{ path: 'f:\\PROJECT\\MAIN.ts', label: 'MAIN.ts', dirty: true },
|
||||
],
|
||||
})
|
||||
expect(parseSnapshot(value, now)?.documents).toHaveLength(1)
|
||||
})
|
||||
})
|
||||
|
||||
describe('combineSnapshots', () => {
|
||||
it('deduplicates paths and prefers dirty metadata', () => {
|
||||
const clean = parseSnapshot(snapshot('F:\\project\\main.ts'), now)!
|
||||
const dirty = parseSnapshot(snapshot('f:\\PROJECT\\main.ts', {
|
||||
instanceId: 'visualstudio-b', ide: 'visualstudio', documents: [{ path: 'f:\\PROJECT\\main.ts', label: 'main.ts', dirty: true }],
|
||||
}), now)!
|
||||
const result = combineSnapshots([clean, dirty])
|
||||
expect(result.total).toBe(1)
|
||||
expect(result.items[0]?.dirty).toBe(true)
|
||||
})
|
||||
|
||||
it('matches basename before path and caps the result', () => {
|
||||
const parsed = parseSnapshot(snapshot('F:\\project\\main.ts', {
|
||||
documents: [
|
||||
{ path: 'F:\\project\\src\\main.ts', label: 'main.ts', dirty: false },
|
||||
{ path: 'F:\\project\\main-helper.ts', label: 'main-helper.ts', dirty: false },
|
||||
],
|
||||
}), now)!
|
||||
expect(combineSnapshots([parsed], 'main', 1).items).toHaveLength(1)
|
||||
expect(combineSnapshots([parsed], 'main', 10).total).toBe(2)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,25 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { candidateFromItem, serializeIdeReference } from '../src/client.js'
|
||||
|
||||
const item = {
|
||||
path: 'F:\\project\\a&b.ts',
|
||||
label: 'a&b.ts',
|
||||
dirty: true,
|
||||
ide: 'vscode' as const,
|
||||
instanceId: 'one',
|
||||
workspace: 'F:\\project',
|
||||
instanceOpenCount: 4,
|
||||
}
|
||||
|
||||
describe('client reference projection', () => {
|
||||
it('keeps the path in a structured JSON payload', () => {
|
||||
expect(serializeIdeReference(item.path)).toBe('<ide-open-file>{"path":"F:\\\\project\\\\a&b.ts"}</ide-open-file>')
|
||||
})
|
||||
|
||||
it('shows IDE, count and dirty state', () => {
|
||||
const candidate = candidateFromItem(item)
|
||||
expect(candidate.description).toContain('VS Code')
|
||||
expect(candidate.description).toContain('4 open')
|
||||
expect(candidate.description).toContain('unsaved changes')
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"declaration": true,
|
||||
"emitDeclarationOnly": true,
|
||||
"outDir": "lib/types"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "NodeNext",
|
||||
"moduleResolution": "NodeNext",
|
||||
"strict": true,
|
||||
"noUncheckedIndexedAccess": true,
|
||||
"exactOptionalPropertyTypes": true,
|
||||
"skipLibCheck": true,
|
||||
"rootDir": "src",
|
||||
"types": ["node"]
|
||||
},
|
||||
"include": ["src/**/*.ts"]
|
||||
}
|
||||
Reference in New Issue
Block a user