chalk-talk 0.13.0 → 0.14.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 +1 -1
- package/assets/player.html +3 -3
- package/embed-mermaid.js +1 -1
- package/embed.js +5 -5
- package/kit/SKILL.template.md +55 -0
- package/kit/browser.mjs +198 -0
- package/kit/format.check.ts +23 -0
- package/kit/format.ts +39 -0
- package/kit/page.mjs +15 -0
- package/kit/tools/audit.mjs +42 -0
- package/kit/tools/checks.mjs +67 -0
- package/kit/tools/eval-metrics.mjs +70 -0
- package/kit/tools/gallery.mjs +31 -0
- package/kit/tools/linux-check.mjs +31 -0
- package/kit/tools/vendor.mjs +24 -0
- package/kit/tools/zip.mjs +14 -0
- package/markdown.js +1 -1
- package/mount.global.js +374 -374
- package/mount.js +1 -1
- package/package.json +4 -2
- package/player.js +4 -4
- package/scripts/browser.mjs +22 -5
- package/scripts/build.mjs +17 -5
- package/scripts/contract.mjs +1 -1
- package/scripts/mermaid.mjs +1 -1
- package/scripts/page.mjs +15 -0
package/scripts/browser.mjs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// reads back what the page wrote in <pre id="chalk-<mode>">. It talks to the browser over the DevTools protocol on a
|
|
3
3
|
// pipe (--remote-debugging-pipe) and waits for that <pre> itself: --dump-dom hangs on some machines, the pipe does not.
|
|
4
4
|
// The browsers are tried in order; one that does not start or does not answer in time gives way to the next.
|
|
5
|
-
//
|
|
5
|
+
// Part of the skills kit (chalk-talk/kit): copied into each skill by its tools/assets.mjs. Node only, no dependency.
|
|
6
6
|
import { execFileSync, spawn } from 'node:child_process'
|
|
7
7
|
import { existsSync, mkdtempSync, readdirSync, rmSync } from 'node:fs'
|
|
8
8
|
import { createRequire } from 'node:module'
|
|
@@ -121,7 +121,7 @@ async function start(exe) {
|
|
|
121
121
|
|
|
122
122
|
/** Opens `file` at `#mode` (with `query`) in a tab of its own and returns what the page wrote, parsed — { error } when
|
|
123
123
|
* the page could not be opened or wrote nothing in `ms`. */
|
|
124
|
-
async function open(b, file, mode, query, ms) {
|
|
124
|
+
async function open(b, file, mode, query, ms, shot) {
|
|
125
125
|
const until = Date.now() + ms
|
|
126
126
|
let sessionId
|
|
127
127
|
// Every call waits at most until the page's time is up: a page busy forever does not hold the build.
|
|
@@ -133,8 +133,24 @@ async function open(b, file, mode, query, ms) {
|
|
|
133
133
|
const { targetId } = await ask('Target.createTarget', { url: 'about:blank' })
|
|
134
134
|
try {
|
|
135
135
|
;({ sessionId } = await ask('Target.attachToTarget', { targetId, flatten: true }))
|
|
136
|
-
|
|
136
|
+
if (shot) await ask('Emulation.setDeviceMetricsOverride', { width: shot.width, height: shot.height, deviceScaleFactor: 1, mobile: false })
|
|
137
|
+
if (shot?.scheme) await ask('Emulation.setEmulatedMedia', { features: [{ name: 'prefers-color-scheme', value: shot.scheme }] })
|
|
138
|
+
const nav = await ask('Page.navigate', { url: `${pathToFileURL(file).href}${query ? `?${query}` : ''}${mode ? `#${mode}` : ''}` })
|
|
137
139
|
if (nav.errorText) return { error: `the page did not open (${nav.errorText})` }
|
|
140
|
+
// A picture of the page as it is once loaded, its fonts in and (when it says so) data-ready on the body.
|
|
141
|
+
if (shot) {
|
|
142
|
+
const ready = "document.readyState === 'complete' && document.fonts.status === 'loaded' && (document.body?.dataset.ready ?? '1') === '1'"
|
|
143
|
+
while (Date.now() < until) {
|
|
144
|
+
const r = await ask('Runtime.evaluate', { expression: ready, returnByValue: true })
|
|
145
|
+
if (r.result?.value === true) {
|
|
146
|
+
await pause(shot.settle ?? 400)
|
|
147
|
+
const { data } = await ask('Page.captureScreenshot', { format: 'png' })
|
|
148
|
+
return { png: `data:image/png;base64,${data}` }
|
|
149
|
+
}
|
|
150
|
+
await pause(50)
|
|
151
|
+
}
|
|
152
|
+
throw new BrowserFailed(`the page was not ready in ${Math.round(ms / 1000)} s`)
|
|
153
|
+
}
|
|
138
154
|
while (Date.now() < until) {
|
|
139
155
|
const r = await ask('Runtime.evaluate', { expression: `document.getElementById('chalk-${mode}')?.textContent ?? null`, returnByValue: true })
|
|
140
156
|
const text = r.result?.value
|
|
@@ -149,7 +165,8 @@ async function open(b, file, mode, query, ms) {
|
|
|
149
165
|
}
|
|
150
166
|
}
|
|
151
167
|
|
|
152
|
-
/** Runs `jobs` ({ file, mode, query }) in one browser
|
|
168
|
+
/** Runs `jobs` ({ file, mode, query, shot }) in one browser — `shot: { width, height }` answers with { png } (a
|
|
169
|
+
* picture of the page once loaded) instead of what the page wrote — and returns their results in order, with the browser used —
|
|
153
170
|
* `results` null when there is no browser at all. A browser that fails (does not start, stops, or a page gets no answer
|
|
154
171
|
* in `timeout` seconds) gives way to the next one, which runs every job again; when none works, each result is
|
|
155
172
|
* { error } naming what was tried. */
|
|
@@ -162,7 +179,7 @@ export async function runPages(jobs, { timeout = 20 } = {}) {
|
|
|
162
179
|
try {
|
|
163
180
|
b = await start(exe)
|
|
164
181
|
const results = []
|
|
165
|
-
for (const { file, mode, query = '' } of jobs) results.push(await open(b, file, mode, query, timeout * 1000))
|
|
182
|
+
for (const { file, mode = '', query = '', shot } of jobs) results.push(await open(b, file, mode, query, timeout * 1000, shot))
|
|
166
183
|
return { results, used: exe, tried }
|
|
167
184
|
} catch (e) {
|
|
168
185
|
tried.push(`${exe}: ${e instanceof BrowserFailed ? e.message : e.message.split('\n')[0]}`)
|
package/scripts/build.mjs
CHANGED
|
@@ -21,6 +21,7 @@ import { tmpdir } from 'node:os'
|
|
|
21
21
|
import { basename, dirname, join, resolve } from 'node:path'
|
|
22
22
|
import { fileURLToPath } from 'node:url'
|
|
23
23
|
import { runPages } from './browser.mjs'
|
|
24
|
+
import { fill, inline } from './page.mjs'
|
|
24
25
|
import { ChalkTextError, chaptersFor, parseChalk, PROP_ALIASES, PROP_NAMES, validateScene } from './contract.mjs'
|
|
25
26
|
import { syntaxForLine, syntaxForStep } from './syntax.mjs'
|
|
26
27
|
|
|
@@ -98,9 +99,7 @@ const slug = slugOf(scenes[0], sources[0])
|
|
|
98
99
|
const work = asSvg ? mkdtempSync(join(tmpdir(), 'chalk-talk-svg-')) : undefined
|
|
99
100
|
const out = work ? join(work, 'page.html') : resolve(outArg ?? join(dirname(resolve(sources[0])), `${slug}.html`))
|
|
100
101
|
const content = course ? { course: option('--title') ?? scenes[0].title, lessons: scenes } : scenes[0]
|
|
101
|
-
|
|
102
|
-
const recipe = JSON.stringify(content).replace(/</g, '\\u003c')
|
|
103
|
-
const page = readFileSync(join(here, '../assets/player.html'), 'utf8').replace('{{RECIPE}}', () => recipe)
|
|
102
|
+
const page = fill(readFileSync(join(here, '../assets/player.html'), 'utf8'), { RECIPE: inline(content) })
|
|
104
103
|
writeFileSync(out, page)
|
|
105
104
|
|
|
106
105
|
scenes.forEach((scene, k) => {
|
|
@@ -109,12 +108,17 @@ scenes.forEach((scene, k) => {
|
|
|
109
108
|
console.log(`✓ contract${who}${scene.steps.length} steps, ${chaptersFor(scene).length} chapters, ${values} values`)
|
|
110
109
|
})
|
|
111
110
|
|
|
111
|
+
// Onda C: a figure made with `define` is seen by nobody when the build passes at once (the review measures, it does
|
|
112
|
+
// not see): with one, the snapshots are made anyway and the build asks for one look at the contact sheet.
|
|
113
|
+
const defined = [...new Set(scenes.flatMap((s) => Object.keys(s.defs ?? {})))]
|
|
114
|
+
const snapshots = snapAt >= 0 || (defined.length > 0 && !asSvg)
|
|
115
|
+
|
|
112
116
|
// One browser for everything the page has to do: the review, the snapshots and the SVGs, lesson by lesson.
|
|
113
117
|
const query = (k) => (course ? `lesson=${k + 1}` : '')
|
|
114
118
|
const jobs = []
|
|
115
119
|
scenes.forEach((_, k) => {
|
|
116
120
|
if (!noReview) jobs.push({ what: 'review', k, file: out, mode: 'review', query: query(k) })
|
|
117
|
-
if (
|
|
121
|
+
if (snapshots) jobs.push({ what: 'snapshots', k, file: out, mode: 'snapshots', query: query(k) })
|
|
118
122
|
if (asSvg) jobs.push({ what: 'svg', k, file: out, mode: 'svg', query: query(k) })
|
|
119
123
|
})
|
|
120
124
|
const ran = jobs.length ? await runPages(jobs, { timeout }) : { results: [] }
|
|
@@ -137,11 +141,16 @@ else {
|
|
|
137
141
|
}
|
|
138
142
|
}
|
|
139
143
|
|
|
140
|
-
if (
|
|
144
|
+
if (snapshots) {
|
|
141
145
|
const folder = resolve(snapFolder ?? join(dirname(out), `${slug}.snapshots`))
|
|
142
146
|
for (let k = 0; k < scenes.length; k++) {
|
|
143
147
|
const r = result('snapshots', k)
|
|
144
148
|
if (!r || r.error) {
|
|
149
|
+
// asked for: an error; made only for a defined figure: said, and the page is still delivered
|
|
150
|
+
if (snapAt < 0) {
|
|
151
|
+
console.log(`• figures ${defined.length} defined (${defined.join(', ')}): no browser to draw them here — look at them in the lesson`)
|
|
152
|
+
break
|
|
153
|
+
}
|
|
145
154
|
console.error(`✗ snapshots ${r ? r.error : 'no Chrome, Chromium or Edge here to draw them (set CHROME_PATH)'}`)
|
|
146
155
|
code = 4
|
|
147
156
|
break
|
|
@@ -154,6 +163,9 @@ if (snapAt >= 0) {
|
|
|
154
163
|
const sheet = join(into, `${lesson}.contact-sheet.png`)
|
|
155
164
|
png(`${lesson}.contact-sheet.png`, r.sheet)
|
|
156
165
|
console.log(`→ ${sheet} (${r.chapters} ${r.chapters === 1 ? 'chapter' : 'chapters'} × ${r.settings} ${r.settings === 1 ? 'setting' : 'settings'})`)
|
|
166
|
+
if (defined.length && k === scenes.length - 1) {
|
|
167
|
+
console.log(`• figures ${defined.length} defined (${defined.join(', ')}): open the contact sheet once and check ${defined.length === 1 ? 'it reads' : 'they read'} right`)
|
|
168
|
+
}
|
|
157
169
|
}
|
|
158
170
|
}
|
|
159
171
|
|
package/scripts/contract.mjs
CHANGED