flowey_lib_common/
install_nodejs.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Globally install `nodejs`
5
6use flowey::node::prelude::*;
7
8flowey_request! {
9    pub enum Request {
10        /// Automatically install all required nodejs tools and components.
11        ///
12        /// This must be set to true/false when running locally.
13        AutoInstall(bool),
14        /// Which version of nodejs to install (e.g: `6.0.0`)
15        Version(String),
16        /// Ensure node is installed
17        EnsureInstalled(WriteVar<SideEffect>),
18    }
19}
20
21new_flow_node!(struct Node);
22
23impl FlowNode for Node {
24    type Request = Request;
25
26    fn imports(ctx: &mut ImportCtx<'_>) {
27        ctx.import::<crate::ado_task_npm_authenticate::Node>();
28    }
29
30    fn emit(requests: Vec<Self::Request>, ctx: &mut NodeCtx<'_>) -> anyhow::Result<()> {
31        let mut auto_install = None;
32        let mut version = None;
33        let mut done = Vec::new();
34
35        for req in requests {
36            match req {
37                Request::AutoInstall(v) => {
38                    same_across_all_reqs("AutoInstall", &mut auto_install, v)?
39                }
40                Request::Version(v) => same_across_all_reqs("Version", &mut version, v)?,
41                Request::EnsureInstalled(v) => done.push(v),
42            }
43        }
44
45        // don't require specifying a NodeVersion if no one requested node to be
46        // installed
47        if done.is_empty() {
48            return Ok(());
49        }
50
51        let auto_install = auto_install;
52        let version = version.ok_or(anyhow::anyhow!("Missing essential request: NodeVersion"))?;
53        let done = done;
54
55        // -- end of req processing -- //
56
57        let is_installed = match ctx.backend() {
58            FlowBackend::Local => {
59                let auto_install = auto_install
60                    .ok_or(anyhow::anyhow!("Missing essential request: AutoInstall"))?;
61
62                let check_nodejs_install = {
63                    move |_: &mut RustRuntimeServices<'_>| {
64                        if which::which("node").is_err() {
65                            anyhow::bail!("did not find `node` on $PATH");
66                        }
67
68                        // FUTURE: we should also be performing version checks
69                        //
70                        // FUTURE: check if `nvm` is available, and if so, hook
71                        // into `nvm` infra to check for the node version
72                        // (instead of just relying on whatever `node` is
73                        // currently on the $PATH)
74
75                        anyhow::Ok(())
76                    }
77                };
78
79                if auto_install {
80                    ctx.emit_rust_step("installing nodejs", |_vars| {
81                        move |rt| {
82                            if check_nodejs_install(rt).is_ok() {
83                                return Ok(());
84                            }
85
86                            log::warn!("automatic nodejs installation is not supported yet!");
87                            log::warn!(
88                                "follow the guide, and manually ensure you have nodejs installed"
89                            );
90                            log::warn!("  ensure you have nodejs version {version} installed");
91                            log::warn!("press <enter> to continue");
92                            let _ = std::io::stdin().read_line(&mut String::new());
93
94                            check_nodejs_install(rt)?;
95                            Ok(())
96                        }
97                    })
98                } else {
99                    ctx.emit_rust_step("detecting nodejs install", |_vars| {
100                        move |rt| {
101                            check_nodejs_install(rt)?;
102                            Ok(())
103                        }
104                    })
105                }
106            }
107            FlowBackend::Ado => {
108                if !auto_install.unwrap_or(true) {
109                    anyhow::bail!("AutoInstall must be `true` when running on ADO")
110                }
111
112                let auth_done = ctx.reqv(crate::ado_task_npm_authenticate::Request::Done);
113
114                let (did_install, claim_did_install) = ctx.new_var();
115                ctx.emit_ado_step("Install nodejs", |ctx| {
116                    auth_done.claim(ctx);
117                    claim_did_install.claim(ctx);
118                    move |_| {
119                        format!(
120                            r#"
121                                - task: UseNode@1
122                                  inputs:
123                                    version: '{version}'
124                            "#
125                        )
126                    }
127                });
128                did_install
129            }
130            FlowBackend::Github => {
131                if !auto_install.unwrap_or(true) {
132                    anyhow::bail!("AutoInstall must be `true` when running on Github")
133                }
134
135                ctx.emit_gh_step("Install nodejs", "actions/setup-node@v4")
136                    .with("node-version", version)
137                    .finish(ctx)
138            }
139        };
140
141        ctx.emit_side_effect_step([is_installed], done);
142
143        Ok(())
144    }
145}