Skip to main content

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