@kamishibai/sdk 0.1.1
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/README.md +192 -0
- package/package.json +54 -0
- package/src/blocks/board.js +210 -0
- package/src/blocks/callout.js +63 -0
- package/src/blocks/code.js +28 -0
- package/src/blocks/deck.js +76 -0
- package/src/blocks/diagram.js +265 -0
- package/src/blocks/element.js +51 -0
- package/src/blocks/graph.js +264 -0
- package/src/blocks/grid.js +156 -0
- package/src/blocks/index.js +106 -0
- package/src/blocks/list.js +50 -0
- package/src/blocks/placement.js +119 -0
- package/src/blocks/prose.js +28 -0
- package/src/blocks/quote.js +25 -0
- package/src/blocks/raw.js +47 -0
- package/src/blocks/registry.js +158 -0
- package/src/blocks/schema-parts.js +19 -0
- package/src/blocks/section.js +53 -0
- package/src/blocks/slide.js +104 -0
- package/src/blocks/stat.js +83 -0
- package/src/blocks/table.js +50 -0
- package/src/blocks/timeline.js +80 -0
- package/src/cli/commands/close.js +51 -0
- package/src/cli/commands/comments.js +73 -0
- package/src/cli/commands/debug.js +34 -0
- package/src/cli/commands/example.js +22 -0
- package/src/cli/commands/export.js +10 -0
- package/src/cli/commands/init.js +53 -0
- package/src/cli/commands/lint.js +93 -0
- package/src/cli/commands/list.js +54 -0
- package/src/cli/commands/open.js +31 -0
- package/src/cli/commands/promote.js +38 -0
- package/src/cli/commands/render.js +31 -0
- package/src/cli/commands/replay.js +49 -0
- package/src/cli/commands/schema.js +10 -0
- package/src/cli/commands/serve.js +178 -0
- package/src/cli/commands/setup.js +64 -0
- package/src/cli/commands/snapshot.js +29 -0
- package/src/cli/commands/templates.js +75 -0
- package/src/cli/deliver.js +54 -0
- package/src/cli/emit.js +29 -0
- package/src/cli/format.js +153 -0
- package/src/cli/index.js +365 -0
- package/src/cli/registry.js +18 -0
- package/src/core/blocks.js +147 -0
- package/src/core/diagram.js +282 -0
- package/src/core/errors.js +156 -0
- package/src/core/example.js +109 -0
- package/src/core/ir.js +62 -0
- package/src/core/lint-gates.js +427 -0
- package/src/core/lint.js +281 -0
- package/src/core/scan.js +84 -0
- package/src/core/schema.js +88 -0
- package/src/core/spec-check.js +33 -0
- package/src/core/validate.js +44 -0
- package/src/core/version.js +18 -0
- package/src/core/vocabulary.js +140 -0
- package/src/delivery/atomic.js +71 -0
- package/src/delivery/comments.js +180 -0
- package/src/delivery/home.js +70 -0
- package/src/delivery/open.js +31 -0
- package/src/delivery/project.js +141 -0
- package/src/delivery/read.js +109 -0
- package/src/delivery/run.js +95 -0
- package/src/delivery/scaffold-blueprints.js +728 -0
- package/src/delivery/store.js +219 -0
- package/src/delivery/template-extensions.js +183 -0
- package/src/delivery/template-format.js +112 -0
- package/src/delivery/template-package.js +376 -0
- package/src/delivery/template-promote.js +240 -0
- package/src/delivery/template-scaffold.js +181 -0
- package/src/delivery/templates.js +192 -0
- package/src/delivery/toml.js +195 -0
- package/src/delivery/write.js +35 -0
- package/src/export/browser.js +130 -0
- package/src/export/index.js +96 -0
- package/src/export/pdf.js +25 -0
- package/src/export/png.js +40 -0
- package/src/export/pptx.js +48 -0
- package/src/export/slides.js +33 -0
- package/src/export/snapshot.js +33 -0
- package/src/layouts/article.js +103 -0
- package/src/layouts/canvas.js +144 -0
- package/src/layouts/card.js +128 -0
- package/src/layouts/deck.js +88 -0
- package/src/layouts/index.js +90 -0
- package/src/layouts/one-page.js +161 -0
- package/src/layouts/registry.js +251 -0
- package/src/layouts/resume.js +172 -0
- package/src/layouts/template-index.js +78 -0
- package/src/parser/artifact.js +38 -0
- package/src/parser/container.js +103 -0
- package/src/parser/index.js +223 -0
- package/src/parser/tokens.js +265 -0
- package/src/render/board-filter.client.js +80 -0
- package/src/render/compile.js +29 -0
- package/src/render/context.js +98 -0
- package/src/render/element.js +32 -0
- package/src/render/fonts.js +129 -0
- package/src/render/graph-hover.client.js +148 -0
- package/src/render/html.js +52 -0
- package/src/render/index.js +241 -0
- package/src/render/measure.js +60 -0
- package/src/render/placement.js +136 -0
- package/src/render/playback.client.js +74 -0
- package/src/render/scale-to-fit.client.js +136 -0
- package/src/render/scale.js +41 -0
- package/src/render/skeleton.js +131 -0
- package/src/render/ssr.js +24 -0
- package/src/render/styles.js +56 -0
- package/src/render/templates.js +191 -0
- package/src/serve/daemon.js +117 -0
- package/src/serve/overlay.js +213 -0
- package/src/serve/protocol.js +36 -0
- package/src/serve/server.js +264 -0
- package/templates/kami/cards/components.js +40 -0
- package/templates/kami/cards/index.js +25 -0
- package/templates/kami/cards/manifest.js +67 -0
- package/templates/kami/cards/styles.css +389 -0
- package/templates/kami/long-form/components.js +102 -0
- package/templates/kami/long-form/index.js +25 -0
- package/templates/kami/long-form/manifest.js +87 -0
- package/templates/kami/long-form/styles.css +481 -0
- package/templates/kami/one-page/components.js +48 -0
- package/templates/kami/one-page/index.js +27 -0
- package/templates/kami/one-page/manifest.js +65 -0
- package/templates/kami/one-page/styles.css +375 -0
- package/templates/kami/resume/components.js +51 -0
- package/templates/kami/resume/index.js +27 -0
- package/templates/kami/resume/manifest.js +65 -0
- package/templates/kami/resume/styles.css +424 -0
- package/templates/kami/slides/components.js +41 -0
- package/templates/kami/slides/index.js +26 -0
- package/templates/kami/slides/manifest.js +64 -0
- package/templates/kami/slides/styles.css +406 -0
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { existsSync, readFileSync, readSync } from 'node:fs'
|
|
2
|
+
import { basename, extname, resolve } from 'node:path'
|
|
3
|
+
import { CODES, EXIT, KsbError } from '../core/errors.js'
|
|
4
|
+
|
|
5
|
+
export const STDIN_SPEC = '-'
|
|
6
|
+
|
|
7
|
+
const CHUNK_SIZE = 65536
|
|
8
|
+
/** Per-stall budget: MAX_EAGAIN_RETRIES × RETRY_PAUSE_MS ≈ 10s of writer silence. */
|
|
9
|
+
const MAX_EAGAIN_RETRIES = 5000
|
|
10
|
+
const RETRY_PAUSE_MS = 2
|
|
11
|
+
|
|
12
|
+
/** Block the thread briefly without spinning the CPU. */
|
|
13
|
+
const pause = (ms) => {
|
|
14
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Read stdin synchronously. A shell pipe hands us a non-blocking fd, so EAGAIN
|
|
19
|
+
* simply means "the writer has not produced data yet" and must be retried —
|
|
20
|
+
* `readFileSync(0)` gives up on it and breaks `example doc | render -`.
|
|
21
|
+
*
|
|
22
|
+
* The retry budget is per stall, not per read: it resets whenever bytes arrive,
|
|
23
|
+
* so a slow writer that pauses repeatedly (an upstream agent streaming Markdown,
|
|
24
|
+
* say) is only cut off after a genuine ~10s silence rather than accumulating
|
|
25
|
+
* earlier waits until an arbitrary cap truncates a still-live stream.
|
|
26
|
+
*/
|
|
27
|
+
const readStdin = () => {
|
|
28
|
+
const buffer = Buffer.alloc(CHUNK_SIZE)
|
|
29
|
+
const chunks = []
|
|
30
|
+
let retries = 0
|
|
31
|
+
|
|
32
|
+
for (;;) {
|
|
33
|
+
let read
|
|
34
|
+
try {
|
|
35
|
+
read = readSync(0, buffer, 0, CHUNK_SIZE, null)
|
|
36
|
+
} catch (cause) {
|
|
37
|
+
if (cause.code === 'EOF') break
|
|
38
|
+
if (cause.code === 'EAGAIN' && retries < MAX_EAGAIN_RETRIES) {
|
|
39
|
+
retries += 1
|
|
40
|
+
pause(RETRY_PAUSE_MS)
|
|
41
|
+
continue
|
|
42
|
+
}
|
|
43
|
+
throw new KsbError({
|
|
44
|
+
code: CODES.INPUT_EMPTY,
|
|
45
|
+
message: `could not read stdin: ${cause.message}`,
|
|
46
|
+
exitCode: EXIT.USAGE,
|
|
47
|
+
})
|
|
48
|
+
}
|
|
49
|
+
if (read === 0) break
|
|
50
|
+
retries = 0
|
|
51
|
+
chunks.push(Buffer.from(buffer.subarray(0, read)))
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return Buffer.concat(chunks).toString('utf8')
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Read a source document from a path or from stdin (`-`).
|
|
59
|
+
* @returns {{source: string, origin: string, format: 'markdown'|'json', slug: string}}
|
|
60
|
+
*/
|
|
61
|
+
export function readSource(spec) {
|
|
62
|
+
if (spec === STDIN_SPEC) {
|
|
63
|
+
const source = readStdin()
|
|
64
|
+
return { source, origin: 'stdin', format: sniffFormat(source, ''), slug: 'document' }
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const absolute = resolve(spec)
|
|
68
|
+
if (!existsSync(absolute)) {
|
|
69
|
+
throw new KsbError({
|
|
70
|
+
code: CODES.INPUT_NOT_FOUND,
|
|
71
|
+
message: `input not found: ${spec}`,
|
|
72
|
+
exitCode: EXIT.USAGE,
|
|
73
|
+
})
|
|
74
|
+
}
|
|
75
|
+
const source = readFileSync(absolute, 'utf8')
|
|
76
|
+
const ext = extname(absolute)
|
|
77
|
+
return {
|
|
78
|
+
source,
|
|
79
|
+
origin: absolute,
|
|
80
|
+
format: sniffFormat(source, ext),
|
|
81
|
+
slug: basename(absolute, ext) || 'document',
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Read an existing artifact (or any text file) for inspection. */
|
|
86
|
+
export function readArtifact(spec) {
|
|
87
|
+
const absolute = resolve(spec)
|
|
88
|
+
if (!existsSync(absolute)) {
|
|
89
|
+
throw new KsbError({
|
|
90
|
+
code: CODES.INPUT_NOT_FOUND,
|
|
91
|
+
message: `artifact not found: ${spec}`,
|
|
92
|
+
exitCode: EXIT.USAGE,
|
|
93
|
+
})
|
|
94
|
+
}
|
|
95
|
+
try {
|
|
96
|
+
return { html: readFileSync(absolute, 'utf8'), path: absolute }
|
|
97
|
+
} catch (cause) {
|
|
98
|
+
throw new KsbError({
|
|
99
|
+
code: CODES.ARTIFACT_UNREADABLE,
|
|
100
|
+
message: `could not read artifact ${spec}: ${cause.message}`,
|
|
101
|
+
exitCode: EXIT.VALIDATION,
|
|
102
|
+
})
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function sniffFormat(source, ext) {
|
|
107
|
+
if (ext === '.json') return 'json'
|
|
108
|
+
return source.trimStart().startsWith('{') ? 'json' : 'markdown'
|
|
109
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, rmSync } from 'node:fs'
|
|
2
|
+
import { CODES, EXIT, KsbError } from '../core/errors.js'
|
|
3
|
+
import { writeFileAtomic } from './atomic.js'
|
|
4
|
+
import { runDir, servePidfile } from './home.js'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The serve pidfile (CONTRACT E1 — `run/serve.pid` under the store root, whose
|
|
8
|
+
* one resolver is `./home.js`; the env-var name is spelled only there).
|
|
9
|
+
*
|
|
10
|
+
* G2's promoted trap is the reason this file exists at all: a dev server that
|
|
11
|
+
* outlives its session is invisible — it holds a port, keeps re-rendering, and
|
|
12
|
+
* nothing on screen says so. `close` can only be honest about "everything this
|
|
13
|
+
* SDK started is now stopped" if starting one *records* it.
|
|
14
|
+
*
|
|
15
|
+
* The file holds a JSON **array** of records, not a bare pid, for two reasons:
|
|
16
|
+
* `close` must be able to stop every server this SDK started rather than only
|
|
17
|
+
* the most recent one (the orphan case is precisely the one that hides), and a
|
|
18
|
+
* recycled pid must be distinguishable from the process we meant — the record
|
|
19
|
+
* carries the port and start time so a stale entry can be recognised instead of
|
|
20
|
+
* blindly signalled.
|
|
21
|
+
*
|
|
22
|
+
* `run/` is ephemeral state, never a record: unlike `artifacts/`, deleting it
|
|
23
|
+
* loses nothing but the ability to stop a process that is already gone.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
const runFailed = (path, cause) =>
|
|
27
|
+
new KsbError({
|
|
28
|
+
code: CODES.WRITE_FAILED,
|
|
29
|
+
message: `could not update the serve pidfile at ${path}: ${cause.message}`,
|
|
30
|
+
exitCode: EXIT.VALIDATION,
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
/** Is this pid a live process? Signal 0 tests existence without delivering one. */
|
|
34
|
+
export function isAlive(pid) {
|
|
35
|
+
if (!Number.isInteger(pid) || pid <= 0) return false
|
|
36
|
+
try {
|
|
37
|
+
process.kill(pid, 0)
|
|
38
|
+
return true
|
|
39
|
+
} catch (cause) {
|
|
40
|
+
// EPERM means the process exists but belongs to someone else — still alive.
|
|
41
|
+
return cause?.code === 'EPERM'
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const isRecord = (value) =>
|
|
46
|
+
value !== null && typeof value === 'object' && Number.isInteger(value.pid) && value.pid > 0
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Every recorded server, in start order. A corrupt pidfile yields an empty list
|
|
50
|
+
* rather than throwing: `close` must stay usable exactly when state is broken.
|
|
51
|
+
*
|
|
52
|
+
* @returns {Array<{pid: number, port: number, url: string, input: string, startedAt: string}>}
|
|
53
|
+
*/
|
|
54
|
+
export function readServeRecords(env = process.env) {
|
|
55
|
+
const path = servePidfile(env)
|
|
56
|
+
if (!existsSync(path)) return []
|
|
57
|
+
try {
|
|
58
|
+
const parsed = JSON.parse(readFileSync(path, 'utf8'))
|
|
59
|
+
return Array.isArray(parsed) ? parsed.filter(isRecord) : []
|
|
60
|
+
} catch {
|
|
61
|
+
return []
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const writeRecords = (records, env) => {
|
|
66
|
+
const path = servePidfile(env)
|
|
67
|
+
try {
|
|
68
|
+
if (records.length === 0) {
|
|
69
|
+
rmSync(path, { force: true })
|
|
70
|
+
return records
|
|
71
|
+
}
|
|
72
|
+
mkdirSync(runDir(env), { recursive: true })
|
|
73
|
+
writeFileAtomic(path, `${JSON.stringify(records, null, 2)}\n`)
|
|
74
|
+
} catch (cause) {
|
|
75
|
+
throw runFailed(path, cause)
|
|
76
|
+
}
|
|
77
|
+
return records
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Record a newly started server, dropping entries whose process is gone.
|
|
82
|
+
*
|
|
83
|
+
* Pruning on write rather than on read is deliberate: the pidfile is the thing
|
|
84
|
+
* `close` reads, and a file that accumulates dead pids would make `close`
|
|
85
|
+
* report work it did not do.
|
|
86
|
+
*/
|
|
87
|
+
export function registerServe(record, env = process.env) {
|
|
88
|
+
const live = readServeRecords(env).filter((entry) => isAlive(entry.pid))
|
|
89
|
+
return writeRecords([...live, record], env)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Forget every record (used after `close` has signalled them). */
|
|
93
|
+
export function clearServeRecords(env = process.env) {
|
|
94
|
+
return writeRecords([], env)
|
|
95
|
+
}
|