Skip to main content

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}