Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion docs/guide/rpc.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ Register it in `setup`:

```ts
import { defineDevframe } from 'devframe'
import { getModules } from './rpc/get-modules'
import { getModules } from './rpc/functions/get-modules'

export default defineDevframe({
id: 'my-devframe',
Expand All @@ -53,6 +53,8 @@ export default defineDevframe({
})
```

Place each function in its own file under `src/rpc/functions/`, and barrel them in `src/rpc/index.ts` as `const serverFunctions = [...] as const`. The same array feeds the [type-safe client registry](#type-safe-client-registry) and keeps registration order explicit. When per-file functions need to share setup-time state (channels, shared state handles, loaders), expose it through a `WeakMap<DevToolsNodeContext, T>` in a sibling `src/context.ts`.

### Naming convention

Scope with your devframe id and use kebab-case for the action: `my-devframe:get-modules`, `my-devframe:read-file`, `my-devframe:trigger-rebuild`.
Expand Down
2 changes: 1 addition & 1 deletion examples/files-inspector/src/client/routes/about.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export function About({ rpc, basePath }: { rpc: DevToolsRpcClient, basePath: str
const [cwd, setCwd] = useState<string>('')

useEffect(() => {
rpc.call('devframe-files-inspector:get-cwd' as any).then((r: any) => {
rpc.call('devframe-files-inspector:get-cwd').then((r) => {
setCwd(r.cwd)
})
}, [rpc])
Expand Down
2 changes: 1 addition & 1 deletion examples/files-inspector/src/client/routes/home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export function Home({ rpc }: { rpc: DevToolsRpcClient }) {
async function refresh() {
setLoading(true)
try {
const result = await rpc.call('devframe-files-inspector:list-files' as any) as string[]
const result = await rpc.call('devframe-files-inspector:list-files')
setFiles(result)
}
finally {
Expand Down
27 changes: 4 additions & 23 deletions examples/files-inspector/src/devframe.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import process from 'node:process'
import { fileURLToPath } from 'node:url'
import { defineRpcFunction } from 'devframe'
import { defineDevframe } from 'devframe/types'
import { glob } from 'tinyglobby'
import { serverFunctions } from './rpc/index.ts'

const BASE_PATH = '/__devframe-files-inspector/'
const distDir = fileURLToPath(new URL('../dist/client', import.meta.url))
Expand All @@ -18,25 +16,8 @@ export default defineDevframe({
distDir,
},
spa: { loader: 'none' },
async setup(ctx) {
const targetCwd = process.env.DEVFRAME_E2E_CWD || ctx.cwd

ctx.rpc.register(defineRpcFunction({
name: 'devframe-files-inspector:get-cwd',
type: 'static',
jsonSerializable: true,
handler: () => ({ cwd: targetCwd }),
}))

ctx.rpc.register(defineRpcFunction({
name: 'devframe-files-inspector:list-files',
type: 'query',
jsonSerializable: true,
handler: async () => {
const files = await glob(['*'], { cwd: targetCwd, onlyFiles: true, dot: false })
return files.map(f => f.replace(/\\/g, '/')).sort()
},
snapshot: true,
}))
setup(ctx) {
for (const fn of serverFunctions)
ctx.rpc.register(fn)
},
})
11 changes: 11 additions & 0 deletions examples/files-inspector/src/rpc/functions/get-cwd.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import process from 'node:process'
import { defineRpcFunction } from 'devframe'

export const getCwd = defineRpcFunction({
name: 'devframe-files-inspector:get-cwd',
type: 'static',
jsonSerializable: true,
setup: ctx => ({
handler: () => ({ cwd: process.env.DEVFRAME_E2E_CWD || ctx.cwd }),
}),
})
17 changes: 17 additions & 0 deletions examples/files-inspector/src/rpc/functions/list-files.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import process from 'node:process'
import { defineRpcFunction } from 'devframe'
import { glob } from 'tinyglobby'

export const listFiles = defineRpcFunction({
name: 'devframe-files-inspector:list-files',
type: 'query',
jsonSerializable: true,
snapshot: true,
setup: ctx => ({
handler: async () => {
const cwd = process.env.DEVFRAME_E2E_CWD || ctx.cwd
const files = await glob(['*'], { cwd, onlyFiles: true, dot: false })
return files.map(f => f.replace(/\\/g, '/')).sort()
},
}),
})
9 changes: 9 additions & 0 deletions examples/files-inspector/src/rpc/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import type { RpcDefinitionsToFunctions } from 'devframe/rpc'
import { getCwd } from './functions/get-cwd.ts'
import { listFiles } from './functions/list-files.ts'

export const serverFunctions = [getCwd, listFiles] as const

declare module 'devframe' {
interface DevToolsRpcServerFunctions extends RpcDefinitionsToFunctions<typeof serverFunctions> {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export function SnapshotEnv() {
return
setLoading(true)
try {
const r = await rpc.call('next-runtime-snapshot:env' as any, { pattern: p }) as EnvSnapshot
const r = await rpc.call('next-runtime-snapshot:env', { pattern: p })
setSnap(r)
}
finally {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export function SnapshotMemory() {
return
setLoading(true)
try {
const r = await rpc.call('next-runtime-snapshot:memory' as any) as MemorySnapshot
const r = await rpc.call('next-runtime-snapshot:memory')
setSnap(r)
}
finally {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ export function SnapshotSystem() {
if (!rpc)
return
let active = true
rpc.call('next-runtime-snapshot:system' as any).then((r: unknown) => {
rpc.call('next-runtime-snapshot:system').then((r) => {
if (active)
setInfo(r as SystemInfo)
setInfo(r)
})
return () => {
active = false
Expand Down
131 changes: 7 additions & 124 deletions examples/next-runtime-snapshot/src/devframe.ts
Original file line number Diff line number Diff line change
@@ -1,55 +1,14 @@
import process from 'node:process'
import { fileURLToPath } from 'node:url'
import { defineRpcFunction } from 'devframe'
import { defineDevframe } from 'devframe/types'
import * as v from 'valibot'
import { serverFunctions } from './rpc/index.ts'

export type { EnvEntry, EnvSnapshot } from './rpc/functions/env.ts'
export type { MemorySnapshot } from './rpc/functions/memory.ts'
export type { SystemInfo } from './rpc/functions/system.ts'

const BASE_PATH = '/__next-runtime-snapshot/'
const distDir = fileURLToPath(new URL('../dist/client', import.meta.url))

const SECRET_KEY_PATTERN = /SECRET|TOKEN|KEY|PASSWORD|PASS|AUTH|CREDENTIAL/i

export interface SystemInfo {
node: string
platform: NodeJS.Platform
arch: string
pid: number
cwd: string
startedAt: number
}

export interface MemorySnapshot {
memory: {
rss: number
heapTotal: number
heapUsed: number
external: number
arrayBuffers: number
}
uptimeSeconds: number
capturedAt: number
}

export interface EnvEntry {
key: string
value: string
redacted: boolean
}

export interface EnvSnapshot {
entries: EnvEntry[]
total: number
pattern: string
}

function redact(key: string, value: string): EnvEntry {
if (SECRET_KEY_PATTERN.test(key))
return { key, value: '••••••••', redacted: true }
return { key, value, redacted: false }
}

const startedAt = Date.now()

export default defineDevframe({
id: 'next-runtime-snapshot',
name: 'Next Runtime Snapshot',
Expand All @@ -63,83 +22,7 @@ export default defineDevframe({
},
spa: { loader: 'none' },
setup(ctx) {
ctx.rpc.register(defineRpcFunction({
name: 'next-runtime-snapshot:system',
type: 'static',
jsonSerializable: true,
handler: (): SystemInfo => ({
node: process.version,
platform: process.platform,
arch: process.arch,
pid: process.pid,
cwd: process.cwd(),
startedAt,
}),
}))

ctx.rpc.register(defineRpcFunction({
name: 'next-runtime-snapshot:memory',
type: 'query',
jsonSerializable: true,
handler: (): MemorySnapshot => {
const m = process.memoryUsage()
return {
memory: {
rss: m.rss,
heapTotal: m.heapTotal,
heapUsed: m.heapUsed,
external: m.external,
arrayBuffers: m.arrayBuffers,
},
uptimeSeconds: process.uptime(),
capturedAt: Date.now(),
}
},
}))

const EnvEntrySchema = v.object({
key: v.string(),
value: v.string(),
redacted: v.boolean(),
})

ctx.rpc.register(defineRpcFunction({
name: 'next-runtime-snapshot:env',
type: 'query',
jsonSerializable: true,
args: [v.object({
pattern: v.optional(v.string(), ''),
limit: v.optional(v.pipe(v.number(), v.integer(), v.minValue(1), v.maxValue(500)), 50),
})],
returns: v.object({
entries: v.array(EnvEntrySchema),
total: v.number(),
pattern: v.string(),
}),
handler: ({ pattern, limit }): EnvSnapshot => {
const keys = Object.keys(process.env).sort()
let matched: string[]
if (!pattern) {
matched = keys
}
else {
try {
const regex = new RegExp(pattern, 'i')
matched = keys.filter(k => regex.test(k))
}
// Invalid regex: match nothing rather than silently widening to all
// keys (which could leak vars the redaction heuristic doesn't catch).
catch {
matched = []
}
}
const entries = matched.slice(0, limit).map(k => redact(k, process.env[k] ?? ''))
return {
entries,
total: matched.length,
pattern,
}
},
}))
for (const fn of serverFunctions)
ctx.rpc.register(fn)
},
})
68 changes: 68 additions & 0 deletions examples/next-runtime-snapshot/src/rpc/functions/env.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import process from 'node:process'
import { defineRpcFunction } from 'devframe'
import * as v from 'valibot'

const SECRET_KEY_PATTERN = /SECRET|TOKEN|KEY|PASSWORD|PASS|AUTH|CREDENTIAL/i

export interface EnvEntry {
key: string
value: string
redacted: boolean
}

export interface EnvSnapshot {
entries: EnvEntry[]
total: number
pattern: string
}

function redact(key: string, value: string): EnvEntry {
if (SECRET_KEY_PATTERN.test(key))
return { key, value: '••••••••', redacted: true }
return { key, value, redacted: false }
}

const EnvEntrySchema = v.object({
key: v.string(),
value: v.string(),
redacted: v.boolean(),
})

export const env = defineRpcFunction({
name: 'next-runtime-snapshot:env',
type: 'query',
jsonSerializable: true,
args: [v.object({
pattern: v.optional(v.string(), ''),
limit: v.optional(v.pipe(v.number(), v.integer(), v.minValue(1), v.maxValue(500)), 50),
})],
returns: v.object({
entries: v.array(EnvEntrySchema),
total: v.number(),
pattern: v.string(),
}),
handler: ({ pattern, limit }): EnvSnapshot => {
const keys = Object.keys(process.env).sort()
let matched: string[]
if (!pattern) {
matched = keys
}
else {
try {
const regex = new RegExp(pattern, 'i')
matched = keys.filter(k => regex.test(k))
}
// Invalid regex: match nothing rather than silently widening to all
// keys (which could leak vars the redaction heuristic doesn't catch).
catch {
matched = []
}
}
const entries = matched.slice(0, limit).map(k => redact(k, process.env[k] ?? ''))
return {
entries,
total: matched.length,
pattern,
}
},
})
34 changes: 34 additions & 0 deletions examples/next-runtime-snapshot/src/rpc/functions/memory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import process from 'node:process'
import { defineRpcFunction } from 'devframe'

export interface MemorySnapshot {
memory: {
rss: number
heapTotal: number
heapUsed: number
external: number
arrayBuffers: number
}
uptimeSeconds: number
capturedAt: number
}

export const memory = defineRpcFunction({
name: 'next-runtime-snapshot:memory',
type: 'query',
jsonSerializable: true,
handler: (): MemorySnapshot => {
const m = process.memoryUsage()
return {
memory: {
rss: m.rss,
heapTotal: m.heapTotal,
heapUsed: m.heapUsed,
external: m.external,
arrayBuffers: m.arrayBuffers,
},
uptimeSeconds: process.uptime(),
capturedAt: Date.now(),
}
},
})
Loading
Loading