@lincyaw/web 1.0.0-rc.0 → 1.0.0-rc.2

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.
Files changed (58) hide show
  1. package/README.md +33 -4
  2. package/package.json +20 -19
  3. package/src/app/base.css +117 -0
  4. package/src/app/block-playback.ts +81 -0
  5. package/src/app/block-report.ts +104 -0
  6. package/src/app/document-modules.ts +30 -0
  7. package/src/app/env.d.ts +12 -0
  8. package/src/app/main.ts +198 -0
  9. package/src/app/page-app.ts +166 -0
  10. package/src/app/router.ts +109 -0
  11. package/src/app/server.ts +32 -0
  12. package/src/app/site/home.tsx +117 -0
  13. package/src/app/site/layout.tsx +92 -0
  14. package/src/app/site/search.tsx +22 -0
  15. package/src/app/site.css +1450 -0
  16. package/src/client/document.css +172 -0
  17. package/src/client/host.ts +22 -94
  18. package/src/client/preview-frame.ts +152 -0
  19. package/src/client/runtime.ts +159 -0
  20. package/src/domain/app-config.ts +91 -0
  21. package/src/domain/config.ts +17 -9
  22. package/src/domain/dev-updates.ts +12 -0
  23. package/src/domain/index.ts +17 -7
  24. package/src/domain/landing.ts +83 -0
  25. package/src/domain/links.ts +23 -18
  26. package/src/domain/messages.ts +74 -63
  27. package/src/domain/pages.ts +23 -41
  28. package/src/domain/themes.ts +59 -0
  29. package/src/domain/workspace-home.ts +12 -0
  30. package/src/node/client-modules.ts +92 -0
  31. package/src/node/document-bundle.ts +227 -0
  32. package/src/node/landing-template.ts +34 -0
  33. package/src/node/landing.ts +82 -0
  34. package/src/node/offline.ts +34 -11
  35. package/src/node/publish.ts +83 -15
  36. package/src/node/renderer.ts +78 -117
  37. package/src/node/site-assets.ts +17 -0
  38. package/src/node/site-build.ts +304 -0
  39. package/src/node/site-files.ts +87 -0
  40. package/src/node/site-model.ts +337 -0
  41. package/src/node/site-page.ts +82 -0
  42. package/src/node/site-server.ts +822 -0
  43. package/src/node/slides.ts +135 -0
  44. package/src/node/viewer-app.ts +504 -0
  45. package/src/node/viewer-build.ts +38 -0
  46. package/custom-nav-controls.vue +0 -17
  47. package/src/client/env.d.ts +0 -1
  48. package/src/client/home.ts +0 -182
  49. package/src/client/index.ts +0 -4
  50. package/src/client/page.ts +0 -38
  51. package/src/client/preview.ts +0 -71
  52. package/src/client/style.css +0 -427
  53. package/src/client/theme.ts +0 -322
  54. package/src/node/addons.ts +0 -76
  55. package/src/node/site.ts +0 -349
  56. package/src/node/sites.ts +0 -531
  57. package/src/node/vite.ts +0 -166
  58. package/src/node/vitepress.ts +0 -72
package/src/node/vite.ts DELETED
@@ -1,166 +0,0 @@
1
- import { createHash } from 'node:crypto'
2
- import { readFileSync } from 'node:fs'
3
- import type { AddressInfo } from 'node:net'
4
- import MiniSearch from 'minisearch'
5
- import { localRequestRefusal, tokenize } from '@lincyaw/kernel'
6
- import { IR_ENDPOINT, SOURCE_ENDPOINT } from '../domain/links.ts'
7
-
8
- export const IR_MODULE = 'virtual:presmium/ir'
9
- export const UPDATE_EVENT = 'presmium:update'
10
- const RESOLVED = `\0${IR_MODULE}`
11
-
12
- export function revisionOf(json: string): string {
13
- return createHash('sha256').update(json).digest('hex').slice(0, 16)
14
- }
15
-
16
- export interface UpdateEvent {
17
- update: unknown
18
- base: string
19
- revision: string
20
- }
21
-
22
- export type DevMessage =
23
- | ({ type: 'presmium:update' } & UpdateEvent)
24
- | { type: 'presmium:ready', port: number }
25
- | { type: 'presmium:source', file: string, line: number }
26
- | { type: 'presmium:translations' }
27
-
28
- interface Request { url?: string, method?: string, headers: Record<string, string | string[] | undefined> }
29
- interface Response { statusCode: number, setHeader(name: string, value: string): void, end(body?: string): void }
30
-
31
- interface Module { id: string | null }
32
-
33
- interface DevServer {
34
- config: { server: { allowedHosts?: true | string[] } }
35
- httpServer: { once(event: 'listening' | 'close', listener: () => void): unknown, address(): AddressInfo | string | null } | null
36
- middlewares: { use(path: string, handler: (req: Request, res: Response, next?: () => void) => void): unknown }
37
- moduleGraph: {
38
- getModuleById(id: string): Module | undefined
39
- invalidateModule(module: Module, seen?: Set<Module>, timestamp?: number, isHmr?: boolean): void
40
- }
41
- ws: { send(event: string, data: unknown): void }
42
- reloadModule(module: Module): Promise<void>
43
- }
44
-
45
- const SEARCH_INDEX = '/@localSearchIndex'
46
-
47
- function searchIndex(documentsFile: string): string {
48
- const index = new MiniSearch({ fields: ['title', 'titles', 'text'], storeFields: ['title', 'titles'], tokenize })
49
- index.addAll(JSON.parse(readFileSync(documentsFile, 'utf8')))
50
- return JSON.stringify(index)
51
- }
52
-
53
- function localeFile(file: string, lang: string): string {
54
- return file.replace(/\.json$/, `.${lang}.json`)
55
- }
56
-
57
- export function presmiumFrame(policy: string) {
58
- return {
59
- name: 'presmium:frame',
60
- configureServer(server: DevServer) {
61
- server.middlewares.use('/', (_req, res, next) => {
62
- res.setHeader('Content-Security-Policy', policy)
63
- next?.()
64
- })
65
- },
66
- }
67
- }
68
-
69
- export function presmiumReady() {
70
- return {
71
- name: 'presmium:ready',
72
- configureServer(server: DevServer) {
73
- server.httpServer?.once('listening', () => {
74
- const address = server.httpServer?.address()
75
- if (address && typeof address === 'object') process.send?.({ type: 'presmium:ready', port: address.port } satisfies DevMessage)
76
- })
77
- },
78
- }
79
- }
80
-
81
- export function presmiumIR(irFile: string, searchDocuments: string, endpoints: { ir: string, source: string } = { ir: IR_ENDPOINT, source: SOURCE_ENDPOINT }, locales: string[] = []) {
82
- let dev = false
83
- const names = ['root', ...locales]
84
- const documentsOf = (name: string) => (name === 'root' ? searchDocuments : localeFile(searchDocuments, name))
85
- return {
86
- name: 'presmium:ir',
87
- enforce: 'pre' as const,
88
- resolveId(id: string) {
89
- if (id === IR_MODULE) return RESOLVED
90
- return id.startsWith(`${IR_MODULE}/`) && locales.includes(id.slice(IR_MODULE.length + 1)) ? `\0${id}` : undefined
91
- },
92
- load(id: string) {
93
- if (id === RESOLVED) {
94
- const json = readFileSync(irFile, 'utf8')
95
- const translations = locales.map(lang => `${JSON.stringify(lang)}: () => import(${JSON.stringify(`${IR_MODULE}/${lang}`)})`)
96
- return `export default JSON.parse(${JSON.stringify(json)})\nexport const revision = ${JSON.stringify(revisionOf(json))}\nexport const translations = { ${translations.join(', ')} }`
97
- }
98
- if (id.startsWith(`${RESOLVED}/`)) return `export default JSON.parse(${JSON.stringify(readFileSync(localeFile(irFile, id.slice(RESOLVED.length + 1)), 'utf8'))})`
99
- const module = id.split('?')[0]!
100
- if (module === SEARCH_INDEX) {
101
- const load = (name: string) => dev ? `import(/* @vite-ignore */ ${JSON.stringify(`.${SEARCH_INDEX}${name}?t=`)} + Date.now())` : `import(${JSON.stringify(`${SEARCH_INDEX.slice(1)}${name}`)})`
102
- return `export default { ${names.map(name => `${JSON.stringify(name)}: () => ${load(name)}`).join(', ')} }`
103
- }
104
- const name = module.startsWith(SEARCH_INDEX) ? module.slice(SEARCH_INDEX.length) : undefined
105
- if (name && names.includes(name)) return `export default ${JSON.stringify(searchIndex(documentsOf(name)))}`
106
- return undefined
107
- },
108
- configureServer(server: DevServer) {
109
- dev = true
110
- const refreshSearch = () => {
111
- const index = server.moduleGraph.getModuleById(SEARCH_INDEX)
112
- if (!index) return
113
- for (const name of names) {
114
- const module = server.moduleGraph.getModuleById(`${SEARCH_INDEX}${name}`)
115
- if (module) server.moduleGraph.invalidateModule(module, new Set(), Date.now(), true)
116
- }
117
- void server.reloadModule(index)
118
- }
119
- const receive = (message: DevMessage) => {
120
- if (message?.type !== 'presmium:update' && message?.type !== 'presmium:translations') return
121
- for (const id of [RESOLVED, ...locales.map(lang => `${RESOLVED}/${lang}`)]) {
122
- const module = server.moduleGraph.getModuleById(id)
123
- if (module) server.moduleGraph.invalidateModule(module)
124
- }
125
- if (message.type === 'presmium:update') server.ws.send(UPDATE_EVENT, { update: message.update, base: message.base, revision: message.revision } satisfies UpdateEvent)
126
- refreshSearch()
127
- }
128
- process.on('message', receive)
129
- server.httpServer?.once('close', () => process.off('message', receive))
130
- const refused = (req: { headers: Record<string, string | string[] | undefined> }, res: { statusCode: number, end(body?: string): void }) => {
131
- const reason = localRequestRefusal(req.headers, server.config.server.allowedHosts)
132
- if (reason) {
133
- res.statusCode = 403
134
- res.end(reason)
135
- }
136
- return reason !== undefined
137
- }
138
- server.middlewares.use(endpoints.ir, (req, res) => {
139
- if (refused(req, res)) return
140
- const json = readFileSync(irFile, 'utf8')
141
- res.statusCode = 200
142
- res.setHeader('Content-Type', 'application/json')
143
- res.setHeader('Cache-Control', 'no-store')
144
- res.end(`{"revision":${JSON.stringify(revisionOf(json))},"doc":${json}}`)
145
- })
146
- server.middlewares.use(endpoints.source, (req, res) => {
147
- if (refused(req, res)) return
148
- if (req.method !== 'POST' || req.headers['presmium-request'] !== 'source') {
149
- res.statusCode = 405
150
- res.end()
151
- return
152
- }
153
- const params = new URL(req.url ?? '', 'http://localhost').searchParams
154
- const file = params.get('file')
155
- const line = Number(params.get('line'))
156
- if (file) process.send?.({ type: 'presmium:source', file, line: Number.isInteger(line) && line > 0 ? line : 1 } satisfies DevMessage)
157
- res.statusCode = file ? 204 : 400
158
- res.end()
159
- })
160
- server.httpServer?.once('listening', () => {
161
- const address = server.httpServer?.address()
162
- if (address && typeof address === 'object') process.send?.({ type: 'presmium:ready', port: address.port } satisfies DevMessage)
163
- })
164
- },
165
- }
166
- }
@@ -1,72 +0,0 @@
1
- import { spawn } from 'node:child_process'
2
- import { realpathSync } from 'node:fs'
3
- import type { ChildProcess } from 'node:child_process'
4
- import { createServer } from 'node:net'
5
- import type { AddressInfo } from 'node:net'
6
- import { createRequire } from 'node:module'
7
- import { dirname, join } from 'node:path'
8
- import { TYPESCRIPT_IMPORT } from '@lincyaw/kernel/node'
9
- import type { DevMessage } from './vite.ts'
10
-
11
- function bin(): string {
12
- const require = createRequire(import.meta.url)
13
- const pkg = require.resolve('vitepress/package.json')
14
- return join(dirname(pkg), (require(pkg) as { bin: Record<string, string> }).bin.vitepress!)
15
- }
16
-
17
- export function vitepressBuild(root: string, outDir: string): Promise<number> {
18
- return new Promise((done) => {
19
- const child = spawn(process.execPath, [...TYPESCRIPT_IMPORT, bin(), 'build', realpathSync(root), '--outDir', outDir], { stdio: ['ignore', 2, 2] })
20
- child.on('exit', code => done(code ?? 1))
21
- child.on('error', () => done(1))
22
- })
23
- }
24
-
25
- export function freePort(host: string): Promise<number> {
26
- return new Promise((resolve, reject) => {
27
- const probe = createServer()
28
- probe.once('error', reject)
29
- probe.listen(0, host, () => {
30
- const { port } = probe.address() as AddressInfo
31
- probe.close(() => resolve(port))
32
- })
33
- })
34
- }
35
-
36
- export interface DevProcess {
37
- port: number
38
- send(message: DevMessage): void
39
- close(): Promise<void>
40
- }
41
-
42
- export async function vitepressDev(root: string, port: number | undefined, receive: (message: DevMessage) => void, options: { host?: string, signal?: AbortSignal } = {}): Promise<DevProcess> {
43
- const listen = port === 0 ? await freePort(options.host ?? '127.0.0.1') : port
44
- const args = [...TYPESCRIPT_IMPORT, bin(), 'dev', realpathSync(root), ...(listen ? ['--port', String(listen)] : []), ...(options.host ? ['--host', options.host] : [])]
45
- const child: ChildProcess = spawn(process.execPath, args, { stdio: ['ignore', 'inherit', 'inherit', 'ipc'] })
46
- const exited = new Promise<void>(resolve => child.once('exit', () => resolve()))
47
- const stop = () => {
48
- if (child.exitCode === null) child.kill('SIGTERM')
49
- }
50
- options.signal?.addEventListener('abort', stop, { once: true })
51
- return new Promise((resolve, reject) => {
52
- child.once('error', reject)
53
- child.once('exit', code => reject(new Error(`vitepress dev exited with code ${code}`)))
54
- child.on('message', (message: DevMessage) => {
55
- if (message?.type !== 'presmium:ready') {
56
- receive(message)
57
- return
58
- }
59
- resolve({
60
- port: message.port,
61
- send: m => void child.send(m),
62
- async close() {
63
- options.signal?.removeEventListener('abort', stop)
64
- stop()
65
- const timer = setTimeout(() => child.kill('SIGKILL'), 3000)
66
- await exited
67
- clearTimeout(timer)
68
- },
69
- })
70
- })
71
- })
72
- }