flowey_lib_common/
install_nodejs.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

//! Globally install `nodejs`

use flowey::node::prelude::*;

flowey_request! {
    pub enum Request {
        /// Automatically install all required nodejs tools and components.
        ///
        /// This must be set to true/false when running locally.
        AutoInstall(bool),
        /// Which version of nodejs to install (e.g: `6.0.0`)
        Version(String),
        /// Ensure node is installed
        EnsureInstalled(WriteVar<SideEffect>),
    }
}

new_flow_node!(struct Node);

impl FlowNode for Node {
    type Request = Request;

    fn imports(ctx: &mut ImportCtx<'_>) {
        ctx.import::<crate::ado_task_npm_authenticate::Node>();
    }

    fn emit(requests: Vec<Self::Request>, ctx: &mut NodeCtx<'_>) -> anyhow::Result<()> {
        let mut auto_install = None;
        let mut version = None;
        let mut done = Vec::new();

        for req in requests {
            match req {
                Request::AutoInstall(v) => {
                    same_across_all_reqs("AutoInstall", &mut auto_install, v)?
                }
                Request::Version(v) => same_across_all_reqs("Version", &mut version, v)?,
                Request::EnsureInstalled(v) => done.push(v),
            }
        }

        // don't require specifying a NodeVersion if no one requested node to be
        // installed
        if done.is_empty() {
            return Ok(());
        }

        let auto_install = auto_install;
        let version = version.ok_or(anyhow::anyhow!("Missing essential request: NodeVersion"))?;
        let done = done;

        // -- end of req processing -- //

        let is_installed = match ctx.backend() {
            FlowBackend::Local => {
                let auto_install = auto_install
                    .ok_or(anyhow::anyhow!("Missing essential request: AutoInstall"))?;

                let check_nodejs_install = {
                    move |_: &mut RustRuntimeServices<'_>| {
                        if which::which("node").is_err() {
                            anyhow::bail!("did not find `node` on $PATH");
                        }

                        // FUTURE: we should also be performing version checks
                        //
                        // FUTURE: check if `nvm` is available, and if so, hook
                        // into `nvm` infra to check for the node version
                        // (instead of just relying on whatever `node` is
                        // currently on the $PATH)

                        anyhow::Ok(())
                    }
                };

                if auto_install {
                    ctx.emit_rust_step("installing nodejs", |_vars| {
                        move |rt| {
                            if check_nodejs_install(rt).is_ok() {
                                return Ok(());
                            }

                            log::warn!("automatic nodejs installation is not supported yet!");
                            log::warn!(
                                "follow the guide, and manually ensure you have nodejs installed"
                            );
                            log::warn!("  ensure you have nodejs version {version} installed");
                            log::warn!("press <enter> to continue");
                            let _ = std::io::stdin().read_line(&mut String::new());

                            check_nodejs_install(rt)?;
                            Ok(())
                        }
                    })
                } else {
                    ctx.emit_rust_step("detecting nodejs install", |_vars| {
                        move |rt| {
                            check_nodejs_install(rt)?;
                            Ok(())
                        }
                    })
                }
            }
            FlowBackend::Ado => {
                if !auto_install.unwrap_or(true) {
                    anyhow::bail!("AutoInstall must be `true` when running on ADO")
                }

                let auth_done = ctx.reqv(crate::ado_task_npm_authenticate::Request::Done);

                let (did_install, claim_did_install) = ctx.new_var();
                ctx.emit_ado_step("Install nodejs", |ctx| {
                    auth_done.claim(ctx);
                    claim_did_install.claim(ctx);
                    move |_| {
                        format!(
                            r#"
                                - task: UseNode@1
                                  inputs:
                                    version: '{version}'
                            "#
                        )
                    }
                });
                did_install
            }
            FlowBackend::Github => {
                if !auto_install.unwrap_or(true) {
                    anyhow::bail!("AutoInstall must be `true` when running on Github")
                }

                ctx.emit_gh_step("Install nodejs", "actions/setup-node@v4")
                    .with("node-version", version)
                    .finish(ctx)
            }
        };

        ctx.emit_side_effect_step([is_installed], done);

        Ok(())
    }
}