Oregami
Repositories/oxedyne/daimond

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.
19import { readFileSync, readdirSync, statSync } from 'node:fs';
20import { join as pjoin } from 'node:path';
21import { open } from './harness.mjs';
22
23const 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.
27const PKGS = [];
28for (const name of readdirSync(CACHE)) {
29 for (const ver of readdirSync(pjoin(CACHE, name))) PKGS.push([name, ver]);
30}
31
32function 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
42const supply = [];
43for (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}
48console.log(`serving ${supply.length} package files from ${CACHE}`);
49
50const s = await open({ name: 'typstpkg' });
51const p = s.page;
52await p.waitForTimeout(1200);
53
54const 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
115console.log(JSON.stringify(out, null, 2));
116await s.close();