dmc_transform/builtin/
npm_command.rs1use crate::pipeline::Transformer;
5use crate::visit::{NodeAction, Visitor, walk_root};
6use dmc_diagnostic::Code;
7use dmc_diagnostic::metadata::SourceMeta;
8use dmc_parser::ast::*;
9
10#[derive(Default)]
14pub struct NpmCommand;
15
16impl NpmCommand {
17 pub fn new() -> Self {
18 Self
19 }
20}
21
22impl Transformer for NpmCommand {
23 fn name(&self) -> &str {
24 "npm-command"
25 }
26
27 fn transform(
28 &self,
29 doc: &mut Document,
30 _meta: &SourceMeta,
31 _diag_engine: &mut duck_diagnostic::DiagnosticEngine<Code>,
32 ) {
33 let mut v = Apply;
34 walk_root(&mut doc.children, &mut v);
35 }
36}
37
38struct Apply;
39
40impl NpmCommand {
41 fn derive(value: &str) -> Option<[(&'static str, String); 4]> {
45 let line = value.lines().next()?.trim();
46 if let Some(rest) = line.strip_prefix("npm install") {
47 let pkgs = rest.trim_start();
48 let suffix = if pkgs.is_empty() { String::new() } else { format!(" {pkgs}") };
49 return Some([
50 ("npm", format!("npm install{suffix}")),
51 ("yarn", format!("yarn add{suffix}")),
52 ("pnpm", format!("pnpm add{suffix}")),
53 ("bun", format!("bun add{suffix}")),
54 ]);
55 }
56 if let Some(rest) = line.strip_prefix("npx create-") {
57 let rest = rest.trim();
58 return Some([
59 ("npm", format!("npx create-{rest}")),
60 ("yarn", format!("yarn create {rest}")),
61 ("pnpm", format!("pnpm create {rest}")),
62 ("bun", format!("bunx --bun create-{rest}")),
63 ]);
64 }
65 if let Some(rest) = line.strip_prefix("npx ") {
66 let rest = rest.trim();
67 return Some([
68 ("npm", format!("npx {rest}")),
69 ("yarn", format!("yarn dlx {rest}")),
70 ("pnpm", format!("pnpm dlx {rest}")),
71 ("bun", format!("bunx --bun {rest}")),
72 ]);
73 }
74 None
75 }
76}
77
78impl Visitor for Apply {
79 fn visit_node(&mut self, node: &mut Node) -> NodeAction {
80 let Node::CodeBlock(cb) = node else { return NodeAction::Keep };
81 let Some(variants) = NpmCommand::derive(&cb.value) else { return NodeAction::Keep };
82 let span = cb.span.clone();
83
84 let pm_attrs: Vec<JsxAttr> = variants
85 .iter()
86 .map(|(name, value)| JsxAttr {
87 name: name.to_string(),
88 value: JsxAttrValue::String(value.clone()),
89 span: span.clone(),
90 })
91 .collect();
92
93 let theme_div = |mode: &str| -> Node {
94 Node::JsxElement(JsxElement {
95 name: "div".to_string(),
96 attrs: vec![JsxAttr {
97 name: "data-theme".to_string(),
98 value: JsxAttrValue::String(mode.to_string()),
99 span: span.clone(),
100 }],
101 children: vec![Node::JsxSelfClosing(JsxSelfClosing {
102 name: "PackageManagerTabs".to_string(),
103 attrs: pm_attrs.clone(),
104 span: span.clone(),
105 })],
106 span: span.clone(),
107 })
108 };
109
110 let fragment = Node::JsxElement(JsxElement {
116 name: "div".to_string(),
117 attrs: vec![JsxAttr {
118 name: "data-dmc-fragment".to_string(),
123 value: JsxAttrValue::String(String::new()),
124 span: span.clone(),
125 }],
126 children: vec![theme_div("dark"), theme_div("light")],
127 span,
128 });
129
130 NodeAction::Replace(vec![fragment])
131 }
132}