@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,29 @@
|
|
|
1
|
+
import { usageError } from '../../core/errors.js'
|
|
2
|
+
import { snapshotArtifact } from '../../export/index.js'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* `--slide` arrives as a string from the command line. It is parsed here, at
|
|
6
|
+
* the boundary, so the export layer only ever sees an integer or nothing —
|
|
7
|
+
* `--slide abc` must be a usage error, not a `NaN` that quietly snapshots
|
|
8
|
+
* whatever `handles[NaN - 1]` happens to be.
|
|
9
|
+
*/
|
|
10
|
+
const parseSlide = (value) => {
|
|
11
|
+
if (value === undefined) return undefined
|
|
12
|
+
if (!/^[1-9]\d*$/.test(String(value))) {
|
|
13
|
+
throw usageError(`--slide 必須是正整數,收到 ${JSON.stringify(value)}`)
|
|
14
|
+
}
|
|
15
|
+
return Number(value)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* `kamishibai snapshot <artifact> [-o out.png] [--slide N]`
|
|
20
|
+
*
|
|
21
|
+
* @returns {Promise<{result: object, exitCode: number}>}
|
|
22
|
+
*/
|
|
23
|
+
export async function snapshotCommand(target, options = {}) {
|
|
24
|
+
return await snapshotArtifact({
|
|
25
|
+
target,
|
|
26
|
+
out: options.out,
|
|
27
|
+
slide: parseSlide(options.slide),
|
|
28
|
+
})
|
|
29
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { EXIT } from '../../core/errors.js'
|
|
2
|
+
import { isDraftKey } from '../../delivery/template-format.js'
|
|
3
|
+
import { readTemplatePackages } from '../../delivery/templates.js'
|
|
4
|
+
import { isReservedKey } from '../../render/index.js'
|
|
5
|
+
import { registerBuiltinTemplates } from '../registry.js'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* CONTRACT E3 — the four keys of a template listing entry, in output order,
|
|
9
|
+
* plus F3's three, appended (additive-only: the four never move or change meaning).
|
|
10
|
+
*
|
|
11
|
+
* `layout` because since F2a the 文體 decides the shape of the page, so "which
|
|
12
|
+
* template shall I use?" is unanswerable without it. `hasCode` because the
|
|
13
|
+
* trust model is 安裝即信任 with no sandbox (CONTRACT P5): what a listing still
|
|
14
|
+
* owes a reader is the ability to see which of these packages runs code when it
|
|
15
|
+
* draws, without opening a single file.
|
|
16
|
+
*
|
|
17
|
+
* `reserved` for the same reason one layer further out (封緘 F2). A package
|
|
18
|
+
* planted at `kami/anything` is refused by the resolver — but the listing is
|
|
19
|
+
* where a human decides what to draw with, and until this key existed the
|
|
20
|
+
* squatter appeared there **indistinguishable from the two official packages**:
|
|
21
|
+
* same namespace, same shape, same everything. Refusing it at render time and
|
|
22
|
+
* dressing it as official at listing time is the worst of both, so the listing
|
|
23
|
+
* says what it is. Marked, not filtered: this surface's whole charter is that it
|
|
24
|
+
* reports the disk (E3), and hiding a package a user really does have installed
|
|
25
|
+
* would make the one surface that could warn them the one that concealed it.
|
|
26
|
+
*
|
|
27
|
+
* `draft` is F7a's, appended by the same rule and for the same shape of reason
|
|
28
|
+
* (CONTRACT F7a H1). The 臨摹 loop leaves half-cut skins in the store on purpose
|
|
29
|
+
* — that is what a workspace is — and until this key existed they sat in the
|
|
30
|
+
* listing wearing exactly the face of a finished package. The marker is the
|
|
31
|
+
* store's only way to answer 「這張皮是成品還是我昨天畫到一半的」, and it is the
|
|
32
|
+
* flag `promote` keys on, so listing and command agree by construction.
|
|
33
|
+
*/
|
|
34
|
+
export const TEMPLATE_KEYS = Object.freeze([
|
|
35
|
+
'namespace',
|
|
36
|
+
'name',
|
|
37
|
+
'version',
|
|
38
|
+
'root',
|
|
39
|
+
'layout',
|
|
40
|
+
'hasCode',
|
|
41
|
+
'reserved',
|
|
42
|
+
'draft',
|
|
43
|
+
])
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* `kamishibai templates [--json]` (CONTRACT E3 / VC2).
|
|
47
|
+
*
|
|
48
|
+
* Reads the *store*, not the engine's internal registry, after making sure the
|
|
49
|
+
* built-ins are registered. That order is the point: the listing has to be an
|
|
50
|
+
* answer about what is installed on this machine — a listing computed from the
|
|
51
|
+
* bundled modules would happily report templates whose packages were never
|
|
52
|
+
* written, and the namespace would then be a claim nobody checks.
|
|
53
|
+
*
|
|
54
|
+
* `options` carries no flag this command reads — `--json` is the emitter's, not
|
|
55
|
+
* the command's — but the slot is **not** dead: every command module in this
|
|
56
|
+
* directory takes `(…args, options, env)`, and `src/cli/index.js` passes them
|
|
57
|
+
* positionally. Dropping the parameter would silently slide `options` into
|
|
58
|
+
* `env`, and the listing would then read a store root out of a flags object.
|
|
59
|
+
*
|
|
60
|
+
* @returns {{result: Array<object>, exitCode: number}}
|
|
61
|
+
*/
|
|
62
|
+
export function templatesCommand(options = {}, env = process.env) {
|
|
63
|
+
registerBuiltinTemplates(env)
|
|
64
|
+
const entries = readTemplatePackages(env).map((pkg) => ({
|
|
65
|
+
namespace: pkg.namespace,
|
|
66
|
+
name: pkg.name,
|
|
67
|
+
version: pkg.version,
|
|
68
|
+
root: pkg.root,
|
|
69
|
+
layout: pkg.layout,
|
|
70
|
+
hasCode: pkg.hasCode,
|
|
71
|
+
reserved: isReservedKey(`${pkg.namespace}/${pkg.name}`),
|
|
72
|
+
draft: isDraftKey(`${pkg.namespace}/${pkg.name}`),
|
|
73
|
+
}))
|
|
74
|
+
return { result: entries, exitCode: EXIT.OK }
|
|
75
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { join } from 'node:path'
|
|
2
|
+
import { EXIT } from '../core/errors.js'
|
|
3
|
+
import { resolveProject } from '../delivery/project.js'
|
|
4
|
+
import { archiveArtifact } from '../delivery/store.js'
|
|
5
|
+
import { writeArtifact } from '../delivery/write.js'
|
|
6
|
+
import { registerBuiltinTemplates } from './registry.js'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The delivery chain shared by `render` and `replay` (SPEC §8): one canonical
|
|
10
|
+
* copy into the central store, one delivery copy at the caller's path, and the
|
|
11
|
+
* copy's location recorded in the store index.
|
|
12
|
+
*
|
|
13
|
+
* The project is resolved *before* anything is written: a rejected `--project`
|
|
14
|
+
* must not leave a half-delivered artifact behind.
|
|
15
|
+
*
|
|
16
|
+
* `slides` joins the result only for deck artifacts (CONTRACT C4): a document
|
|
17
|
+
* artifact must keep the pinned four-key shape exactly, so an always-present
|
|
18
|
+
* key set to 0 or null is not an option.
|
|
19
|
+
*
|
|
20
|
+
* @returns {{result: {ok: true, artifact: string, bytes: number, archived: string,
|
|
21
|
+
* slides?: number}, exitCode: number}}
|
|
22
|
+
*/
|
|
23
|
+
export function deliver({
|
|
24
|
+
html,
|
|
25
|
+
slides,
|
|
26
|
+
slug,
|
|
27
|
+
options = {},
|
|
28
|
+
env = process.env,
|
|
29
|
+
cwd = process.cwd(),
|
|
30
|
+
}) {
|
|
31
|
+
const project = resolveProject({ project: options.project, cwd })
|
|
32
|
+
|
|
33
|
+
const outPath = options.out ?? join('out', `${slug}.html`)
|
|
34
|
+
const { path, bytes } = writeArtifact(outPath, html)
|
|
35
|
+
|
|
36
|
+
const { path: archived } = archiveArtifact({
|
|
37
|
+
project: project.name,
|
|
38
|
+
slug,
|
|
39
|
+
html,
|
|
40
|
+
copies: [path],
|
|
41
|
+
env,
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
// First touch of the store registers the built-in template packages (E3), so
|
|
45
|
+
// an Agent that never ran `setup` still finds a populated namespace. It is a
|
|
46
|
+
// no-op once the manifests match, which is the normal case.
|
|
47
|
+
registerBuiltinTemplates(env)
|
|
48
|
+
|
|
49
|
+
const result = { ok: true, artifact: path, bytes, archived }
|
|
50
|
+
return {
|
|
51
|
+
result: Number.isInteger(slides) ? { ...result, slides } : result,
|
|
52
|
+
exitCode: EXIT.OK,
|
|
53
|
+
}
|
|
54
|
+
}
|
package/src/cli/emit.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { EXIT, KsbError, ROOT_PATH, CODES } from '../core/errors.js'
|
|
2
|
+
import { formatResult } from './format.js'
|
|
3
|
+
|
|
4
|
+
/** Write a command result to stdout in the requested shape and set the exit code. */
|
|
5
|
+
export function emit({ command, result, json, exitCode = EXIT.OK }) {
|
|
6
|
+
const text = json ? JSON.stringify(result) : formatResult(command, result)
|
|
7
|
+
process.stdout.write(text.endsWith('\n') ? text : `${text}\n`)
|
|
8
|
+
process.exitCode = exitCode
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** Normalise any thrown value into the pinned failure result object. */
|
|
12
|
+
export function toFailure(error) {
|
|
13
|
+
if (error instanceof KsbError) {
|
|
14
|
+
return { result: { ok: false, errors: [error.toFinding()] }, exitCode: error.exitCode }
|
|
15
|
+
}
|
|
16
|
+
return {
|
|
17
|
+
result: {
|
|
18
|
+
ok: false,
|
|
19
|
+
errors: [
|
|
20
|
+
{
|
|
21
|
+
path: ROOT_PATH,
|
|
22
|
+
code: CODES.PARSE_FAILED,
|
|
23
|
+
message: error?.message ?? String(error),
|
|
24
|
+
},
|
|
25
|
+
],
|
|
26
|
+
},
|
|
27
|
+
exitCode: EXIT.VALIDATION,
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The single formatter. Both output paths (`--json` and human-readable) consume
|
|
3
|
+
* the *same* result object: `--json` serialises it, humans get this rendering.
|
|
4
|
+
* Nothing may be computed here that the JSON path cannot show.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const formatErrors = (errors) =>
|
|
8
|
+
[
|
|
9
|
+
`✖ ${errors.length} 個問題:`,
|
|
10
|
+
...errors.map((e) => ` - [${e.code}] ${e.path}: ${e.message}`),
|
|
11
|
+
].join('\n')
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* `render` / `replay` share one result shape, so they share one rendering.
|
|
15
|
+
* Every extension key has to surface here too — a key that lives only in
|
|
16
|
+
* `--json` is exactly the two-path drift this formatter exists to prevent.
|
|
17
|
+
*/
|
|
18
|
+
const formatDelivery = (verb) => (r) =>
|
|
19
|
+
[
|
|
20
|
+
`✔ 已${verb} → ${r.artifact}(${r.bytes} bytes${
|
|
21
|
+
Number.isInteger(r.slides) ? `,${r.slides} 張` : ''
|
|
22
|
+
})`,
|
|
23
|
+
` 正本 → ${r.archived}`,
|
|
24
|
+
].join('\n')
|
|
25
|
+
|
|
26
|
+
const formatListEntry = (e) =>
|
|
27
|
+
[
|
|
28
|
+
`• ${e.name} ${e.template} ${e.createdAt} ${e.generator}`,
|
|
29
|
+
` 正本 → ${e.artifact}`,
|
|
30
|
+
...e.copies.map((copy) => ` 副本 → ${copy}`),
|
|
31
|
+
].join('\n')
|
|
32
|
+
|
|
33
|
+
const formatList = (entries) =>
|
|
34
|
+
entries.length === 0 ? '(此專案尚無產物)' : entries.map(formatListEntry).join('\n')
|
|
35
|
+
|
|
36
|
+
/** Every key of the pinned export/snapshot shapes has to reach human output too. */
|
|
37
|
+
const formatExport = (r) => `✔ 已匯出 ${r.format} → ${r.artifact}`
|
|
38
|
+
const formatSnapshot = (r) => `✔ 已截圖 → ${r.artifact}(${r.width}×${r.height})`
|
|
39
|
+
const formatSetup = (r) =>
|
|
40
|
+
[
|
|
41
|
+
`✔ 中央儲存庫 → ${r.home}`,
|
|
42
|
+
` 瀏覽器:${r.browser}(安裝指令:${r.command})`,
|
|
43
|
+
].join('\n')
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* S5 surfaces. Every key of each pinned JSON shape has to appear here too —
|
|
47
|
+
* the `--json` path and the human path are the same result object rendered
|
|
48
|
+
* twice, and a key that only one of them shows is the drift this file exists
|
|
49
|
+
* to prevent.
|
|
50
|
+
*/
|
|
51
|
+
const formatServe = (r) =>
|
|
52
|
+
[
|
|
53
|
+
`✔ 預覽伺服器 → ${r.url}`,
|
|
54
|
+
` pid ${r.pid} port ${r.port}`,
|
|
55
|
+
` 正本 → ${r.artifact}`,
|
|
56
|
+
].join('\n')
|
|
57
|
+
|
|
58
|
+
const formatClose = (r) =>
|
|
59
|
+
[
|
|
60
|
+
r.closed.length === 0 ? '✔ 沒有執行中的預覽伺服器' : `✔ 已終止 ${r.closed.join('、')}`,
|
|
61
|
+
...(r.stale.length === 0 ? [] : [` 已清掉的殘留紀錄:${r.stale.join('、')}`]),
|
|
62
|
+
].join('\n')
|
|
63
|
+
|
|
64
|
+
const formatCommentEntry = (e) => `• [${e.status}] ${e.id} ${e.blockId} ${e.ts}\n ${e.text}`
|
|
65
|
+
|
|
66
|
+
/** `comments` lists an array; `comments add|resolve` answer with one entry. */
|
|
67
|
+
const formatComments = (result) => {
|
|
68
|
+
if (!Array.isArray(result)) return formatCommentEntry(result)
|
|
69
|
+
return result.length === 0 ? '(此產物尚無留言)' : result.map(formatCommentEntry).join('\n')
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Every key of the pinned template-listing shape reaches human output too —
|
|
74
|
+
* including F3's two. `hasCode` in particular: it is the one field whose whole
|
|
75
|
+
* purpose is to be *seen* (CONTRACT P5), so letting it live only in `--json`
|
|
76
|
+
* would defeat the field rather than merely drift from it.
|
|
77
|
+
*/
|
|
78
|
+
const formatTemplateEntry = (t) =>
|
|
79
|
+
`• ${t.namespace}/${t.name}@${t.version} root=${t.root === '' ? '(不限)' : t.root}` +
|
|
80
|
+
` layout=${t.layout === '' ? '(未宣告)' : t.layout} ${t.hasCode ? 'hasCode=帶碼' : 'hasCode=純資料'}` +
|
|
81
|
+
(t.reserved ? ' (冒用出廠名,無法載入)' : '') +
|
|
82
|
+
(t.draft ? ' (臨摹工作區草稿,promote 後轉正)' : '')
|
|
83
|
+
|
|
84
|
+
const formatTemplates = (entries) =>
|
|
85
|
+
entries.length === 0 ? '(尚未註冊任何模板包)' : entries.map(formatTemplateEntry).join('\n')
|
|
86
|
+
|
|
87
|
+
/** `init` answers with the package it just wrote and the file to render first. */
|
|
88
|
+
const formatInit = (r) =>
|
|
89
|
+
[
|
|
90
|
+
`✔ 已建立模板包 ${r.template} → ${r.dir}`,
|
|
91
|
+
` 檔案:${r.files.join('、')}`,
|
|
92
|
+
` 開箱即用:kamishibai render ${r.source} -t ${r.template}`,
|
|
93
|
+
].join('\n')
|
|
94
|
+
|
|
95
|
+
/** `promote` answers with where the draft went, and what to draw with now. */
|
|
96
|
+
const formatPromote = (r) =>
|
|
97
|
+
[
|
|
98
|
+
`✔ 已轉正 ${r.from} → ${r.to}(${r.dir})`,
|
|
99
|
+
` manifest 已改寫:${r.manifest}`,
|
|
100
|
+
` ${r.from} 原位已不存在;改用:kamishibai render <來源> -t ${r.to}`,
|
|
101
|
+
].join('\n')
|
|
102
|
+
|
|
103
|
+
const formatDebug = (r) =>
|
|
104
|
+
[
|
|
105
|
+
`✔ 中央儲存庫 → ${r.home}`,
|
|
106
|
+
` 模板包:${r.templates}`,
|
|
107
|
+
` 瀏覽器:${r.browser}`,
|
|
108
|
+
` 引擎:${r.engine}`,
|
|
109
|
+
` 執行中的預覽伺服器:${r.servers}`,
|
|
110
|
+
].join('\n')
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* `lint --rules` lists the gate itself. Same array the `--json` path serialises:
|
|
114
|
+
* a rule that only one of the two paths shows is the drift this file prevents.
|
|
115
|
+
*/
|
|
116
|
+
const formatRuleEntry = (rule) =>
|
|
117
|
+
[
|
|
118
|
+
`• [${rule.code}] scope=${rule.scope} pattern=${rule.pattern}` +
|
|
119
|
+
// F7b: only a gate rule carries a declaration site, and only a gate rule
|
|
120
|
+
// grows the line. The ten rules that were here before F7b render byte for
|
|
121
|
+
// byte as they did, which is what「既有輸出格式不破」means.
|
|
122
|
+
(rule.gate === undefined ? '' : ` gate=${rule.gate}`),
|
|
123
|
+
` ${rule.note}`,
|
|
124
|
+
].join('\n')
|
|
125
|
+
|
|
126
|
+
const formatLint = (r) =>
|
|
127
|
+
Array.isArray(r) ? r.map(formatRuleEntry).join('\n') : `✔ lint 通過:${r.artifact}`
|
|
128
|
+
|
|
129
|
+
const RENDERERS = Object.freeze({
|
|
130
|
+
render: formatDelivery('渲染'),
|
|
131
|
+
replay: formatDelivery('重繪'),
|
|
132
|
+
export: formatExport,
|
|
133
|
+
snapshot: formatSnapshot,
|
|
134
|
+
setup: formatSetup,
|
|
135
|
+
lint: formatLint,
|
|
136
|
+
example: (r) => r.example,
|
|
137
|
+
schema: (r) => JSON.stringify(r, null, 2),
|
|
138
|
+
list: formatList,
|
|
139
|
+
open: (r) => r.path,
|
|
140
|
+
serve: formatServe,
|
|
141
|
+
close: formatClose,
|
|
142
|
+
comments: formatComments,
|
|
143
|
+
templates: formatTemplates,
|
|
144
|
+
init: formatInit,
|
|
145
|
+
promote: formatPromote,
|
|
146
|
+
debug: formatDebug,
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
export function formatResult(command, result) {
|
|
150
|
+
if (result && result.ok === false) return formatErrors(result.errors ?? [])
|
|
151
|
+
const renderer = RENDERERS[command]
|
|
152
|
+
return renderer ? renderer(result) : JSON.stringify(result, null, 2)
|
|
153
|
+
}
|