oxedyne/daimond/dev/probe_typstpkg.mjs
5.0 KiB, 1 run
created by r2519314175:105, which is this file's identity for as long as the history lasts, whatever it is later renamed to
download · who wrote it · its history
| 1 | // probe_typstpkg.mjs — can the vendored compiler be given a Typst package it already has? |
| 2 | // |
| 3 | // The author's book template opens with `#import "@preview/cetz:0.3.4"`. `@preview` is |
| 4 | // the Typst Universe registry, fetched over the network, and the in-browser compiler has |
| 5 | // none -- so gathering every local file still leaves the book uncompilable. Before any |
| 6 | // work is proposed on that, one question has to be answered from the artefact rather than |
| 7 | // from memory: does this build expose a way to SUPPLY a package it is handed? |
| 8 | // |
| 9 | // Three things in `typst_ts_web_compiler.d.ts` say it might: |
| 10 | // |
| 11 | // TypstCompilerBuilder.set_package_registry(context, real_resolve_fn) |
| 12 | // TypstCompilerBuilder.set_access_model(context, mtime, is_file, real_path, read_all) |
| 13 | // ProxyContext.untar(data, cb) "untar a tarball and call a callback for each entry" |
| 14 | // |
| 15 | // This probe wires all three to JavaScript callbacks that log every argument they are |
| 16 | // handed, serves cetz 0.3.4 out of the local Typst cache (READ ONLY -- nothing is fetched |
| 17 | // over the network), and tries to compile a document that imports it. Whether it |
| 18 | // succeeds or fails, the log says what the interface actually wants. |
| 19 | import { readFileSync, readdirSync, statSync } from 'node:fs'; |
| 20 | import { join as pjoin } from 'node:path'; |
| 21 | import { open } from './harness.mjs'; |
| 22 | |
| 23 | const CACHE = process.env.HOME + '/.cache/typst/packages/preview'; |
| 24 | // Everything in the cache, every version. cetz imports oxifmt, and a package that |
| 25 | // resolves but whose own dependency does not is indistinguishable from one that never |
| 26 | // resolved -- the first run of this probe stopped exactly there. |
| 27 | const PKGS = []; |
| 28 | for (const name of readdirSync(CACHE)) { |
| 29 | for (const ver of readdirSync(pjoin(CACHE, name))) PKGS.push([name, ver]); |
| 30 | } |
| 31 | |
| 32 | function walk(root, rel = '') { |
| 33 | const out = []; |
| 34 | for (const name of readdirSync(pjoin(root, rel))) { |
| 35 | const r = rel ? rel + '/' + name : name; |
| 36 | if (statSync(pjoin(root, r)).isDirectory()) { out.push(...walk(root, r)); continue; } |
| 37 | out.push([r, Array.from(readFileSync(pjoin(root, r)))]); |
| 38 | } |
| 39 | return out; |
| 40 | } |
| 41 | |
| 42 | const supply = []; |
| 43 | for (const [name, ver] of PKGS) { |
| 44 | const root = pjoin(CACHE, name, ver); |
| 45 | try { statSync(root); } catch (e) { console.log('not cached: ' + name + ':' + ver); continue; } |
| 46 | for (const [rel, bytes] of walk(root)) supply.push([`/pkg/preview/${name}/${ver}/${rel}`, bytes]); |
| 47 | } |
| 48 | console.log(`serving ${supply.length} package files from ${CACHE}`); |
| 49 | |
| 50 | const s = await open({ name: 'typstpkg' }); |
| 51 | const p = s.page; |
| 52 | await p.waitForTimeout(1200); |
| 53 | |
| 54 | const out = await p.evaluate(async (supply) => { |
| 55 | const VENDOR = new URL('/vendor/typst/', location.href); |
| 56 | const mod = await import(new URL('typst_ts_web_compiler.mjs', VENDOR).href); |
| 57 | await mod.default(new URL('typst_ts_web_compiler_bg.wasm', VENDOR)); |
| 58 | |
| 59 | const files = new Map(); |
| 60 | for (const [path, bytes] of supply) files.set(path, new Uint8Array(bytes)); |
| 61 | const log = []; |
| 62 | const note = (what, args) => { |
| 63 | if (log.length < 40) log.push(what + '(' + args.map(a => |
| 64 | typeof a === 'string' ? JSON.stringify(a) : Object.prototype.toString.call(a)).join(', ') + ')'); |
| 65 | }; |
| 66 | |
| 67 | const b = new mod.TypstCompilerBuilder(); |
| 68 | let wired = 'ok'; |
| 69 | try { |
| 70 | await b.set_access_model({}, |
| 71 | (...a) => { note('mtime', a); return 0; }, |
| 72 | (...a) => { note('is_file', a); return files.has(String(a[a.length - 1])); }, |
| 73 | (...a) => { note('real_path', a); return String(a[a.length - 1]); }, |
| 74 | (...a) => { |
| 75 | note('read_all', a); |
| 76 | const f = files.get(String(a[a.length - 1])); |
| 77 | if (!f) throw new Error('no such file: ' + a[a.length - 1]); |
| 78 | return f; |
| 79 | }); |
| 80 | await b.set_package_registry({}, (...a) => { |
| 81 | note('resolve', a); |
| 82 | // The spec arrives somehow; answer with the directory the files are under. |
| 83 | const j = JSON.stringify(a.map(x => typeof x === 'string' ? x : (x && x.name ? x : null))); |
| 84 | log.push('resolve-arg-shape ' + j.slice(0, 300)); |
| 85 | const spec = a.find(x => x && typeof x === 'object' && x.name) || null; |
| 86 | if (spec) return `/pkg/${spec.namespace || 'preview'}/${spec.name}/${spec.version}`; |
| 87 | return '/pkg/preview/cetz/0.3.4'; |
| 88 | }); |
| 89 | } catch (e) { |
| 90 | wired = 'threw: ' + (e && e.message ? e.message : e); |
| 91 | } |
| 92 | for (const n of ['LibertinusSerif-Regular.otf', 'LibertinusSerif-Bold.otf', |
| 93 | 'LibertinusSerif-Italic.otf', 'LibertinusSerif-BoldItalic.otf', 'NewCMMath-Regular.otf']) { |
| 94 | const r = await fetch(new URL('fonts/' + n, VENDOR)); |
| 95 | await b.add_raw_font(new Uint8Array(await r.arrayBuffer())); |
| 96 | } |
| 97 | let res = {}; |
| 98 | try { |
| 99 | const c = await b.build(); |
| 100 | c.reset_shadow(); |
| 101 | c.add_source('/m.typ', '#import "@preview/cetz:0.3.4"\n' |
| 102 | + '#cetz.canvas({ import cetz.draw: *; circle((0,0), radius: 1) })\n'); |
| 103 | const ret = c.compile('/m.typ', undefined, 'pdf', 3); |
| 104 | const pdf = ret instanceof Uint8Array ? ret : (ret && (ret.result || ret.artifact)); |
| 105 | res.bytes = pdf ? pdf.length : 0; |
| 106 | res.diag = ret && ret.diagnostics ? JSON.stringify(ret.diagnostics).slice(0, 600) : '(none)'; |
| 107 | } catch (e) { |
| 108 | res.built = 'threw: ' + (e && e.message ? e.message : e); |
| 109 | } |
| 110 | res.wired = wired; |
| 111 | res.log = log; |
| 112 | return res; |
| 113 | }, supply); |
| 114 | |
| 115 | console.log(JSON.stringify(out, null, 2)); |
| 116 | await s.close(); |