@lincyaw/kernel 0.0.0-stage → 1.0.0-rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/README.md +32 -2
- package/package.json +37 -3
- package/src/anchors.ts +54 -0
- package/src/blocks.ts +27 -0
- package/src/checks.ts +35 -0
- package/src/contracts/agent.ts +153 -0
- package/src/contracts/execution.ts +119 -0
- package/src/contracts/operations.ts +8 -0
- package/src/contracts/preview.ts +391 -0
- package/src/contracts/server.ts +158 -0
- package/src/contracts/service.ts +8 -0
- package/src/contracts/speech.ts +137 -0
- package/src/contracts.ts +285 -0
- package/src/errors.ts +7 -0
- package/src/events.ts +32 -0
- package/src/execution.ts +21 -0
- package/src/fingerprint.ts +31 -0
- package/src/follow.ts +37 -0
- package/src/git.ts +12 -0
- package/src/html.ts +41 -0
- package/src/index.ts +29 -0
- package/src/ir.ts +318 -0
- package/src/kernel.ts +177 -0
- package/src/lock.ts +85 -0
- package/src/logging.ts +11 -0
- package/src/markdown.ts +311 -0
- package/src/node/index.ts +133 -0
- package/src/node/strip-types.mjs +15 -0
- package/src/published.ts +11 -0
- package/src/reading.ts +91 -0
- package/src/requests.ts +28 -0
- package/src/runner.py +454 -0
- package/src/services.ts +146 -0
package/src/contracts.ts
ADDED
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
import type { AgentTool } from './contracts/agent.ts'
|
|
2
|
+
import type { EnvironmentProvider, EnvironmentSpec, Executor } from './contracts/execution.ts'
|
|
3
|
+
import type { NodeServiceContext, ServerContext, ServerContribution } from './contracts/server.ts'
|
|
4
|
+
import { defineService } from './contracts/service.ts'
|
|
5
|
+
import type { ServiceToken } from './contracts/service.ts'
|
|
6
|
+
import type { DirectiveSpec, IRDocument, IRPass, Renderer, RendererContribution, RoleSpec } from './ir.ts'
|
|
7
|
+
|
|
8
|
+
export * from './contracts/agent.ts'
|
|
9
|
+
export * from './contracts/execution.ts'
|
|
10
|
+
export * from './contracts/operations.ts'
|
|
11
|
+
export * from './contracts/preview.ts'
|
|
12
|
+
export * from './contracts/server.ts'
|
|
13
|
+
export * from './contracts/service.ts'
|
|
14
|
+
export * from './contracts/speech.ts'
|
|
15
|
+
|
|
16
|
+
export const KERNEL_VERSION = '1.0.0'
|
|
17
|
+
|
|
18
|
+
export type Capability = 'fs' | 'network' | 'exec' | 'git'
|
|
19
|
+
|
|
20
|
+
export interface ServiceRequirement {
|
|
21
|
+
token: ServiceToken<unknown>
|
|
22
|
+
optional?: boolean
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface PluginManifest {
|
|
26
|
+
id: string
|
|
27
|
+
version: string
|
|
28
|
+
kernel: string
|
|
29
|
+
provides?: ServiceToken<unknown>[]
|
|
30
|
+
requires?: ServiceRequirement[]
|
|
31
|
+
capabilities?: Capability[]
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export type PresentationMode = 'slides' | 'document'
|
|
35
|
+
|
|
36
|
+
export interface Logger {
|
|
37
|
+
debug(...args: unknown[]): void
|
|
38
|
+
info(...args: unknown[]): void
|
|
39
|
+
warn(...args: unknown[]): void
|
|
40
|
+
error(...args: unknown[]): void
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export type Unsubscribe = () => void
|
|
44
|
+
|
|
45
|
+
export interface DomainEvent<Type extends string = string, Payload = unknown> {
|
|
46
|
+
type: Type
|
|
47
|
+
payload: Payload
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface EventBus {
|
|
51
|
+
emit(event: DomainEvent): void
|
|
52
|
+
on<E extends DomainEvent>(type: E['type'], handler: (event: E) => void): Unsubscribe
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface PluginContext {
|
|
56
|
+
config: Record<string, unknown>
|
|
57
|
+
mode: PresentationMode
|
|
58
|
+
provide<T>(token: ServiceToken<T>, impl: T): void
|
|
59
|
+
get<T>(token: ServiceToken<T>): T
|
|
60
|
+
tryGet<T>(token: ServiceToken<T>): T | undefined
|
|
61
|
+
events: EventBus
|
|
62
|
+
log: Logger
|
|
63
|
+
environments?: Readonly<Record<string, EnvironmentSpec>>
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface PluginDefinition {
|
|
67
|
+
manifest: PluginManifest
|
|
68
|
+
setup(ctx: PluginContext): void | Promise<void>
|
|
69
|
+
dispose?(): void | Promise<void>
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export type PluginStatus = 'started' | 'degraded' | 'disabled' | 'failed'
|
|
73
|
+
|
|
74
|
+
export interface PluginReport {
|
|
75
|
+
id: string
|
|
76
|
+
status: PluginStatus
|
|
77
|
+
reason?: string
|
|
78
|
+
missing?: string[]
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export interface StartReport {
|
|
82
|
+
order: string[]
|
|
83
|
+
plugins: PluginReport[]
|
|
84
|
+
cycles: string[][]
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export type KernelEvent =
|
|
88
|
+
| DomainEvent<'kernel.plugin.started', { id: string }>
|
|
89
|
+
| DomainEvent<'kernel.plugin.degraded', { id: string; missing: string[] }>
|
|
90
|
+
| DomainEvent<'kernel.plugin.disabled', { id: string; reason: string }>
|
|
91
|
+
| DomainEvent<'kernel.plugin.failed', { id: string; error: string }>
|
|
92
|
+
|
|
93
|
+
export type Locale = 'zh-CN' | 'en'
|
|
94
|
+
|
|
95
|
+
export const DEFAULT_LANG = 'zh-CN'
|
|
96
|
+
|
|
97
|
+
export function localeOf(lang: string | null | undefined): Locale | undefined {
|
|
98
|
+
const tag = lang?.trim().toLowerCase() ?? ''
|
|
99
|
+
if (tag === 'zh' || tag.startsWith('zh-')) return 'zh-CN'
|
|
100
|
+
if (tag === 'en' || tag.startsWith('en-')) return 'en'
|
|
101
|
+
return undefined
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function deckLocale(lang?: string): Locale {
|
|
105
|
+
return localeOf(lang || DEFAULT_LANG) ?? 'en'
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export interface StepRange {
|
|
109
|
+
from: number
|
|
110
|
+
to?: number
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export interface StepState {
|
|
114
|
+
active: boolean
|
|
115
|
+
reached: boolean
|
|
116
|
+
done: boolean
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function stepStateAt(range: StepRange, step: number): StepState {
|
|
120
|
+
const reached = step >= range.from
|
|
121
|
+
const done = range.to !== undefined && step >= range.to
|
|
122
|
+
return { active: reached && !done, reached, done }
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export interface Cursor {
|
|
126
|
+
slide: number
|
|
127
|
+
step: number
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export type AnchorString = `${string}:${string}`
|
|
131
|
+
|
|
132
|
+
export type ResolutionStatus = 'exact' | 'moved' | 'fuzzy' | 'lost'
|
|
133
|
+
|
|
134
|
+
export interface ResolvedLocation {
|
|
135
|
+
file: string
|
|
136
|
+
startLine: number
|
|
137
|
+
endLine: number
|
|
138
|
+
text: string
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export interface Resolution {
|
|
142
|
+
status: ResolutionStatus
|
|
143
|
+
location?: ResolvedLocation
|
|
144
|
+
fingerprint?: string
|
|
145
|
+
candidates?: ResolvedLocation[]
|
|
146
|
+
message?: string
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export interface SourceReader {
|
|
150
|
+
read(file: string, ref?: string): Promise<string | undefined>
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export interface RecordedAnchor {
|
|
154
|
+
fingerprint: string
|
|
155
|
+
file: string
|
|
156
|
+
startLine: number
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export interface AnchorResolver {
|
|
160
|
+
scheme: string
|
|
161
|
+
resolve(anchor: AnchorString, src: SourceReader, recorded?: RecordedAnchor, ref?: string): Promise<Resolution>
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export interface AnchorResolverRegistry {
|
|
165
|
+
register(resolver: AnchorResolver): Unsubscribe
|
|
166
|
+
resolve(anchor: AnchorString, src: SourceReader, recorded?: RecordedAnchor, ref?: string): Promise<Resolution>
|
|
167
|
+
schemes(): string[]
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export interface AnchorLockFile {
|
|
171
|
+
version: 1
|
|
172
|
+
anchors: Record<string, RecordedAnchor>
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export function isRenderable(resolution: Resolution): boolean {
|
|
176
|
+
return resolution.status === 'exact' || resolution.status === 'moved'
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export type Severity = 'error' | 'warning' | 'info'
|
|
180
|
+
|
|
181
|
+
export interface Finding {
|
|
182
|
+
checkId: string
|
|
183
|
+
severity: Severity
|
|
184
|
+
message: string
|
|
185
|
+
source?: { file: string; line?: number }
|
|
186
|
+
hint?: string
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export interface CheckContext {
|
|
190
|
+
deckDir: string
|
|
191
|
+
workspaceRoot: string
|
|
192
|
+
headmatter: Record<string, unknown>
|
|
193
|
+
src: SourceReader
|
|
194
|
+
resolvers: AnchorResolverRegistry
|
|
195
|
+
lock: AnchorLockFile
|
|
196
|
+
ir: IRDocument
|
|
197
|
+
service?<T>(token: ServiceToken<T>): T | undefined
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export interface Check {
|
|
201
|
+
id: string
|
|
202
|
+
run(ctx: CheckContext): Promise<Finding[]>
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export interface PresmiumNodePlugin {
|
|
206
|
+
id: string
|
|
207
|
+
version?: string
|
|
208
|
+
kernel?: string
|
|
209
|
+
resolvers?: AnchorResolver[]
|
|
210
|
+
checks?: Check[]
|
|
211
|
+
anchors?(ctx: CheckContext): AnchorString[] | Promise<AnchorString[]>
|
|
212
|
+
directives?: DirectiveSpec[]
|
|
213
|
+
passes?: IRPass[]
|
|
214
|
+
executors?: Executor[]
|
|
215
|
+
renderer?: Renderer
|
|
216
|
+
rendererExtensions?: Record<string, RendererContribution>
|
|
217
|
+
vitePlugins?(ctx: { deckDir: string, workspaceRoot: string, config: Record<string, unknown> }): unknown[]
|
|
218
|
+
services?(ctx: NodeServiceContext): [ServiceToken<unknown>, unknown][]
|
|
219
|
+
environments?: Record<string, EnvironmentProvider>
|
|
220
|
+
server?(ctx: ServerContext): ServerContribution | Promise<ServerContribution>
|
|
221
|
+
roles?: RoleSpec[]
|
|
222
|
+
tools?: AgentTool[]
|
|
223
|
+
operator?: boolean
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export type Role = 'presenter' | 'learner'
|
|
227
|
+
|
|
228
|
+
export interface Participant {
|
|
229
|
+
id: string
|
|
230
|
+
name: string
|
|
231
|
+
role: Role
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export type SessionStatus = 'idle' | 'active' | 'ended'
|
|
235
|
+
|
|
236
|
+
export interface SessionSnapshot {
|
|
237
|
+
sessionId?: string
|
|
238
|
+
status: SessionStatus
|
|
239
|
+
me?: Participant
|
|
240
|
+
participants: Participant[]
|
|
241
|
+
presenterCursor?: Cursor
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
export interface SessionService {
|
|
245
|
+
snapshot(): SessionSnapshot
|
|
246
|
+
subscribe(listener: (snapshot: SessionSnapshot) => void): Unsubscribe
|
|
247
|
+
create(name: string): Promise<string>
|
|
248
|
+
join(sessionId: string, name: string): Promise<void>
|
|
249
|
+
end(): Promise<void>
|
|
250
|
+
leave(): void
|
|
251
|
+
archive?(): Promise<string>
|
|
252
|
+
readonly personal?: ClassroomStore
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
export const SESSION = defineService<SessionService>('presmium.session')
|
|
256
|
+
|
|
257
|
+
export interface StoredItem<T> {
|
|
258
|
+
id: string
|
|
259
|
+
value: T
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
export interface ClassroomStore {
|
|
263
|
+
list<T>(collection: string): StoredItem<T>[]
|
|
264
|
+
get<T>(collection: string, id: string): T | undefined
|
|
265
|
+
put<T>(collection: string, id: string, value: T): void
|
|
266
|
+
remove(collection: string, id: string): void
|
|
267
|
+
subscribe(collection: string, listener: () => void): Unsubscribe
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
export const CLASSROOM_STORE = defineService<ClassroomStore>('presmium.classroomStore')
|
|
271
|
+
|
|
272
|
+
export interface PresenterPanel {
|
|
273
|
+
id: string
|
|
274
|
+
title: string
|
|
275
|
+
component: unknown
|
|
276
|
+
order?: number
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
export interface PresenterPanels {
|
|
280
|
+
register(panel: PresenterPanel): Unsubscribe
|
|
281
|
+
list(): PresenterPanel[]
|
|
282
|
+
subscribe(listener: () => void): Unsubscribe
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
export const PRESENTER_PANELS = defineService<PresenterPanels>('presmium.presenterPanels')
|
package/src/errors.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export function errorMessage(error: unknown): string {
|
|
2
|
+
return error instanceof Error ? error.message : String(error)
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export function isRecord(value: unknown): value is Record<string, unknown> {
|
|
6
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value)
|
|
7
|
+
}
|
package/src/events.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { DomainEvent, EventBus } from './contracts.ts'
|
|
2
|
+
|
|
3
|
+
type Handler = (event: DomainEvent) => void
|
|
4
|
+
type ErrorHandler = (error: unknown, event: DomainEvent) => void
|
|
5
|
+
|
|
6
|
+
const reportToConsole: ErrorHandler = (error, event) => {
|
|
7
|
+
console.error(`[presmium] handler for "${event.type}" threw`, error)
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function createEventBus(onError: ErrorHandler = reportToConsole): EventBus {
|
|
11
|
+
const handlers = new Map<string, Set<Handler>>()
|
|
12
|
+
return {
|
|
13
|
+
emit(event) {
|
|
14
|
+
for (const handler of [...(handlers.get(event.type) ?? [])]) {
|
|
15
|
+
try {
|
|
16
|
+
handler(event)
|
|
17
|
+
} catch (error) {
|
|
18
|
+
onError(error, event)
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
on(type, handler) {
|
|
23
|
+
const set = handlers.get(type) ?? new Set()
|
|
24
|
+
handlers.set(type, set)
|
|
25
|
+
const h = handler as Handler
|
|
26
|
+
set.add(h)
|
|
27
|
+
return () => {
|
|
28
|
+
set.delete(h)
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
}
|
|
32
|
+
}
|
package/src/execution.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { RunResult } from './contracts.ts'
|
|
2
|
+
|
|
3
|
+
const ALIASES: Record<string, string> = {
|
|
4
|
+
py: 'python',
|
|
5
|
+
python3: 'python',
|
|
6
|
+
js: 'javascript',
|
|
7
|
+
mjs: 'javascript',
|
|
8
|
+
ts: 'typescript',
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function languageId(lang: string): string {
|
|
12
|
+
const id = lang.trim().toLowerCase()
|
|
13
|
+
if (id.startsWith('ipython')) return 'python'
|
|
14
|
+
return ALIASES[id] ?? id
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function failedRun(executorId: string, stderr: string, durationMs = 0): RunResult {
|
|
18
|
+
return { ok: false, stdout: '', stderr, exitCode: null, durationMs, timedOut: false, source: 'live', executorId }
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const CREDENTIAL_VARIABLE = /_(?:KEY|TOKEN|SECRET|PASSWORD|APPID)$/i
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export function normalizeWhitespace(text: string): string {
|
|
2
|
+
const lines = text
|
|
3
|
+
.replace(/\r\n?/g, '\n')
|
|
4
|
+
.split('\n')
|
|
5
|
+
.map(line => line.replace(/\s+$/u, '').replace(/^[ \t]+/, lead => ' '.repeat(lead.replace(/\t/g, ' ').length)))
|
|
6
|
+
.filter(line => line.length > 0)
|
|
7
|
+
if (lines.length === 0) return ''
|
|
8
|
+
const indent = Math.min(...lines.map(line => line.length - line.trimStart().length))
|
|
9
|
+
return lines
|
|
10
|
+
.map((line) => {
|
|
11
|
+
const depth = line.length - line.trimStart().length - indent
|
|
12
|
+
const body = line.trimStart().replace(/\s+/gu, ' ').replace(/ ?([^\p{L}\p{N}_ ]) ?/gu, '$1')
|
|
13
|
+
return ' '.repeat(depth) + body
|
|
14
|
+
})
|
|
15
|
+
.join('\n')
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const FNV_OFFSET = 0xcbf29ce484222325n
|
|
19
|
+
const FNV_PRIME = 0x100000001b3n
|
|
20
|
+
|
|
21
|
+
export function fnv1a64(text: string): string {
|
|
22
|
+
let hash = FNV_OFFSET
|
|
23
|
+
for (const byte of new TextEncoder().encode(text)) {
|
|
24
|
+
hash = BigInt.asUintN(64, (hash ^ BigInt(byte)) * FNV_PRIME)
|
|
25
|
+
}
|
|
26
|
+
return hash.toString(16).padStart(16, '0')
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function fingerprint(text: string): string {
|
|
30
|
+
return fnv1a64(normalizeWhitespace(text))
|
|
31
|
+
}
|
package/src/follow.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { IRBlock, IRSection } from './ir.ts'
|
|
2
|
+
|
|
3
|
+
export type StepKeys = string[][]
|
|
4
|
+
|
|
5
|
+
export function stepKeys(section: Pick<IRSection, 'blocks' | 'steps'>, renderer?: string): StepKeys {
|
|
6
|
+
const keys: StepKeys = Array.from({ length: section.steps + 1 }, () => [])
|
|
7
|
+
const add = (step: number, key: string) => step > 0 && keys[step]?.push(key)
|
|
8
|
+
let left = false
|
|
9
|
+
const visit = (blocks: readonly IRBlock[], prefix: string) => {
|
|
10
|
+
for (const block of blocks) {
|
|
11
|
+
if (renderer !== undefined && block.only && !block.only.includes(renderer)) {
|
|
12
|
+
left = true
|
|
13
|
+
continue
|
|
14
|
+
}
|
|
15
|
+
const id = prefix + block.id
|
|
16
|
+
if (block.role === 'visual') {
|
|
17
|
+
for (let k = 0; k <= (block.span ?? 0); k++) add(block.at + k, k === 0 ? id : `${id}+${k}`)
|
|
18
|
+
if (block.until !== undefined) add(block.until, `${id}-`)
|
|
19
|
+
}
|
|
20
|
+
if (block.node.type === 'component' && block.node.children) visit(block.node.children, `${id}/`)
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
visit(section.blocks, '')
|
|
24
|
+
return left ? keys.filter((step, i) => i === 0 || step.length > 0) : keys
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function stepTrail(keys: StepKeys, step: number): string[] {
|
|
28
|
+
return keys.slice(1, step + 1).flat()
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function trailStep(keys: StepKeys, trail: readonly string[]): number {
|
|
32
|
+
const reached = new Set(trail)
|
|
33
|
+
for (let step = keys.length - 1; step > 0; step--) {
|
|
34
|
+
if (keys[step]!.some(key => reached.has(key))) return step
|
|
35
|
+
}
|
|
36
|
+
return 0
|
|
37
|
+
}
|
package/src/git.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export const GIT_SAFE_CONFIG: readonly string[] = ['core.hooksPath=/dev/null', 'core.fsmonitor=false', 'core.quotePath=false', 'commit.gpgSign=false', 'tag.gpgSign=false']
|
|
2
|
+
|
|
3
|
+
export function gitConfigArgs(): string[] {
|
|
4
|
+
return GIT_SAFE_CONFIG.flatMap(entry => ['-c', entry])
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function gitEnvironment(env: Record<string, string | undefined>): Record<string, string> {
|
|
8
|
+
const own: Record<string, string> = {}
|
|
9
|
+
for (const [name, value] of Object.entries(env)) if (value !== undefined && !/^GIT_/i.test(name)) own[name] = value
|
|
10
|
+
own.GIT_TERMINAL_PROMPT = '0'
|
|
11
|
+
return own
|
|
12
|
+
}
|
package/src/html.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { parseFragment } from 'parse5'
|
|
2
|
+
import type { DefaultTreeAdapterMap } from 'parse5'
|
|
3
|
+
|
|
4
|
+
export function escapeHtml(text: string): string {
|
|
5
|
+
return text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"')
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface HtmlAttribute {
|
|
9
|
+
name: string
|
|
10
|
+
value: string
|
|
11
|
+
start: number
|
|
12
|
+
end: number
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const URL_ATTRIBUTES: ReadonlySet<string> = new Set(['src', 'poster', 'href'])
|
|
16
|
+
|
|
17
|
+
export function htmlAttributes(html: string, names: ReadonlySet<string>): HtmlAttribute[] {
|
|
18
|
+
const found: HtmlAttribute[] = []
|
|
19
|
+
const visit = (node: DefaultTreeAdapterMap['node']) => {
|
|
20
|
+
if ('attrs' in node) {
|
|
21
|
+
for (const { name, value } of node.attrs) {
|
|
22
|
+
const at = node.sourceCodeLocation?.attrs?.[name]
|
|
23
|
+
if (names.has(name) && at) found.push({ name, value, start: at.startOffset, end: at.endOffset })
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
if ('childNodes' in node) node.childNodes.forEach(visit)
|
|
27
|
+
if ('content' in node) visit(node.content)
|
|
28
|
+
}
|
|
29
|
+
visit(parseFragment(html, { sourceCodeLocationInfo: true }))
|
|
30
|
+
return found.sort((a, b) => a.start - b.start)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function mapHtmlUrls(html: string, map: (url: string, attribute: string) => string | undefined): string {
|
|
34
|
+
let out = html
|
|
35
|
+
for (const { name, value, start, end } of htmlAttributes(html, URL_ATTRIBUTES).reverse()) {
|
|
36
|
+
const mapped = map(value, name)
|
|
37
|
+
if (mapped === undefined) continue
|
|
38
|
+
out = `${out.slice(0, start)}${html.slice(start, start + name.length)}="${escapeHtml(mapped)}"${out.slice(end)}`
|
|
39
|
+
}
|
|
40
|
+
return out
|
|
41
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export * from './contracts.ts'
|
|
2
|
+
export * from './ir.ts'
|
|
3
|
+
export { createAnchorResolverRegistry, parseAnchor } from './anchors.ts'
|
|
4
|
+
export type { ParsedAnchor } from './anchors.ts'
|
|
5
|
+
export { docBlocks, irBlocks, nodeMdast, withProp } from './blocks.ts'
|
|
6
|
+
export { CheckRegistry } from './checks.ts'
|
|
7
|
+
export { errorMessage, isRecord } from './errors.ts'
|
|
8
|
+
export { createEventBus } from './events.ts'
|
|
9
|
+
export { CREDENTIAL_VARIABLE, failedRun, languageId } from './execution.ts'
|
|
10
|
+
export { GIT_SAFE_CONFIG, gitConfigArgs, gitEnvironment } from './git.ts'
|
|
11
|
+
export { escapeHtml, htmlAttributes, mapHtmlUrls, URL_ATTRIBUTES } from './html.ts'
|
|
12
|
+
export type { HtmlAttribute } from './html.ts'
|
|
13
|
+
export { hostName, localRequestRefusal } from './requests.ts'
|
|
14
|
+
export type { RequestHeaders } from './requests.ts'
|
|
15
|
+
export { fingerprint, fnv1a64, normalizeWhitespace } from './fingerprint.ts'
|
|
16
|
+
export { stepKeys, stepTrail, trailStep } from './follow.ts'
|
|
17
|
+
export type { StepKeys } from './follow.ts'
|
|
18
|
+
export { Kernel } from './kernel.ts'
|
|
19
|
+
export type { KernelOptions } from './kernel.ts'
|
|
20
|
+
export { ANCHOR_LOCK_PATH, AnchorLock } from './lock.ts'
|
|
21
|
+
export { prefixed } from './logging.ts'
|
|
22
|
+
export { lowerMyst, roleMark } from './markdown.ts'
|
|
23
|
+
export type { LinkedType, LoweredMarkdown, LowerOptions } from './markdown.ts'
|
|
24
|
+
export { publishedUrl } from './published.ts'
|
|
25
|
+
export type { PublishedUrlOptions } from './published.ts'
|
|
26
|
+
export { blockState, blockText, blockVisible, chapters, isChapterTitle, mdastText, narrationAt, sectionNarration, tokenize } from './reading.ts'
|
|
27
|
+
export type { Chapter } from './reading.ts'
|
|
28
|
+
export { HOST_OWNER, kernelIncompatibility, ServiceRegistry } from './services.ts'
|
|
29
|
+
export type { StartPlan } from './services.ts'
|