bynk_emit/lib.rs
1//! Bynk's build orchestration and TypeScript emission — the layer above
2//! `bynk-check`.
3//!
4//! `project` is the build driver: it conducts discovery, the dependency graph,
5//! consistency, validation, symbols, and paths, and owns `compile_project`.
6//! `emitter` lowers a checked program to TypeScript. Read the crate as "build
7//! orchestration + TS emission" — orchestration drives emission.
8//!
9//! Extracted from `bynkc` as slice 4 of the crate-decomposition track over
10//! `bynk-syntax` + `bynk-check`. Behaviour is unchanged; `bynkc` depends on this
11//! crate and re-exports its modules so its public API (`compile_project`,
12//! `ProjectOutput`, …) and the binary are untouched.
13
14pub mod emitter;
15pub mod project;
16
17use std::path::Path;
18
19use project::{CompiledFile, ProjectOutput};
20
21/// Minimum supported Node.js **major** version for the `node` platform binding
22/// and for running Bynk's emitted TypeScript.
23///
24/// Single source of truth for the Node floor: the emitted code targets it, the
25/// `bynk` driver's `doctor` command compares a detected `node` against it, and
26/// `bynkc`'s CLI re-exports it rather than restating the number. Lives in
27/// `bynk-emit` (which emits the TS that runs on Node) so both binaries share one
28/// definition (slice 7; was a `bynkc` const before the driver dropped that dep).
29pub const NODE_MAJOR_FLOOR: u32 = 18;
30
31/// Write a [`ProjectOutput`]'s files under `dir`, creating parent directories as
32/// needed. The shared writer behind both `bynkc`'s `compile`/`test` paths and
33/// `bynk dev`'s in-process build (slice 7) — so the on-disk result is identical
34/// however the build was driven.
35pub fn write_output(out: &ProjectOutput, dir: &Path) -> std::io::Result<()> {
36 for file in &out.files {
37 write_compiled_file(file, dir)?;
38 }
39 Ok(())
40}
41
42/// Write a single [`CompiledFile`] under `dir`, map-aware: a `.bynk`-sourced file
43/// gets a sibling `.ts.map` and a `//# sourceMappingURL` trailer (slice 1, ADR
44/// 0103); a file with no map is written verbatim. Shared by [`write_output`] and
45/// `bynkc test`'s output loops, so every disk-writing path emits maps uniformly
46/// (slice 2 — `bynkc test --inspect` runs the emitted `.ts` directly and needs
47/// the maps on disk). The trailer lives only on the on-disk artefact; the
48/// in-memory `file.typescript` stays trailer-free, so golden comparisons are
49/// unaffected. The map name appends `.map` to the output file name.
50pub fn write_compiled_file(file: &CompiledFile, dir: &Path) -> std::io::Result<()> {
51 let target = dir.join(&file.output_path);
52 if let Some(parent) = target.parent() {
53 std::fs::create_dir_all(parent)?;
54 }
55 match &file.source_map {
56 Some(map) => {
57 let map_name = match target.file_name() {
58 Some(n) => format!("{}.map", n.to_string_lossy()),
59 None => "module.ts.map".to_string(),
60 };
61 let map_path = target.with_file_name(&map_name);
62 std::fs::write(&map_path, map)?;
63 let with_trailer = format!("{}//# sourceMappingURL={map_name}\n", file.typescript);
64 std::fs::write(&target, with_trailer)?;
65 }
66 None => std::fs::write(&target, &file.typescript)?,
67 }
68 // Slice 3 (ADR 0105): the debug-metadata sidecar — a `<file>.bynkdbg.json` next
69 // to the `.ts`, mapping each emitted handler to its Bynk operation label so the
70 // debugger names stack frames in Bynk. A sibling like the `.ts.map`; not bundled
71 // into a deployed Worker.
72 if let Some(meta) = &file.debug_metadata {
73 let meta_name = match target.file_name() {
74 Some(n) => format!("{}.bynkdbg.json", n.to_string_lossy()),
75 None => "module.ts.bynkdbg.json".to_string(),
76 };
77 std::fs::write(target.with_file_name(meta_name), meta)?;
78 }
79 Ok(())
80}