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
146
147
148
149
150
151
152
153
154
155
156
use std::collections::HashMap;
use std::fmt::Display;
use std::fs;
use std::path::{Path, PathBuf};

use askama::Template;
use pathdiff::diff_paths;

use crate::configuration_file::ConfigurationFile;
use crate::io::FromFileError;
use crate::monorepo_manifest::{EnumeratePackageManifestsError, MonorepoManifest};
use crate::package_manifest::PackageManifest;

#[derive(Template)]
#[template(path = "makefile")]

struct MakefileTemplate<'a> {
    root: &'a str,
    output_file: &'a str,
    package_directory: &'a str,
    scoped_package_name: &'a str,
    unscoped_package_name: &'a str,
    internal_dependency_package_json_filenames_inclusive: &'a Vec<String>,
    create_pack_target: &'a bool,
    npm_pack_archive_dependencies: &'a HashMap<String, String>,
    internal_npm_dependencies_exclusive: &'a Vec<&'a str>,
}

#[derive(Debug)]
#[non_exhaustive]
pub struct MakeDependencyMakefileError {
    pub kind: MakeDependencyMakefileErrorKind,
}

impl Display for MakeDependencyMakefileError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "error creating package makefile")
    }
}

impl std::error::Error for MakeDependencyMakefileError {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        match &self.kind {
            MakeDependencyMakefileErrorKind::FromFile(err) => Some(err),
            MakeDependencyMakefileErrorKind::EnumeratePackageManifests(err) => Some(err),
        }
    }
}

impl From<FromFileError> for MakeDependencyMakefileError {
    fn from(err: FromFileError) -> Self {
        Self {
            kind: MakeDependencyMakefileErrorKind::FromFile(err),
        }
    }
}

impl From<EnumeratePackageManifestsError> for MakeDependencyMakefileError {
    fn from(err: EnumeratePackageManifestsError) -> Self {
        Self {
            kind: MakeDependencyMakefileErrorKind::EnumeratePackageManifests(err),
        }
    }
}

#[derive(Debug)]
pub enum MakeDependencyMakefileErrorKind {
    #[non_exhaustive]
    FromFile(FromFileError),
    #[non_exhaustive]
    EnumeratePackageManifests(EnumeratePackageManifestsError),
}

pub fn make_dependency_makefile(
    root: &Path,
    package_directory: &Path,
    output_file: &Path,
    create_pack_target: bool,
) -> Result<(), MakeDependencyMakefileError> {
    let lerna_manifest = MonorepoManifest::from_directory(root)?;
    let package_manifest = PackageManifest::from_directory(root, package_directory)?;

    // determine the complete set of internal dependencies (and self!)
    let package_manifest_by_package_name = lerna_manifest.package_manifests_by_package_name()?;

    let internal_dependencies_exclusive: Vec<_> = package_manifest
        .transitive_internal_dependency_package_names_exclusive(&package_manifest_by_package_name)
        .collect();

    let internal_dependency_package_json_filenames_inclusive: Vec<PathBuf> = {
        let mut dependency_dirs = internal_dependencies_exclusive
            .iter()
            .map(|internal_dependency| (*internal_dependency).path())
            .collect::<Vec<_>>();
        dependency_dirs.push(package_manifest.path());
        dependency_dirs
    };

    let npm_pack_archive_dependencies = &internal_dependencies_exclusive
        .iter()
        .map(|dependency| {
            let target_directory = package_manifest
                .directory()
                .join(".internal-npm-dependencies");
            let target = target_directory
                .join(dependency.npm_pack_file_basename())
                .to_str()
                .expect("npm pack filename is not UTF-8 encodable")
                .to_owned();
            let source_package_directory = dependency.directory();
            let source = diff_paths(source_package_directory, target_directory)
                .expect("No relative path to source package")
                .to_str()
                .expect("Source package path is not UTF-8 encodable")
                .to_owned();
            (target, source)
        })
        .collect();

    // create a string of the makefile contents
    let makefile_contents = MakefileTemplate {
        root: root.to_str().expect("Monorepo root is not UTF_8 encodable"),
        output_file: output_file
            .to_str()
            .expect("Output file is not UTF-8 encodable"),
        package_directory: package_manifest
            .directory()
            .to_str()
            .expect("Package directory is not UTF-8 encodable"),
        scoped_package_name: &package_manifest.contents.name,
        unscoped_package_name: package_manifest.unscoped_package_name(),
        internal_dependency_package_json_filenames_inclusive:
            &internal_dependency_package_json_filenames_inclusive
                .iter()
                .map(|internal_dependency| {
                    internal_dependency
                        .to_str()
                        .expect("Internal package directory is not UTF-8 encodable")
                        .to_owned()
                })
                .collect(),
        create_pack_target: &create_pack_target,
        npm_pack_archive_dependencies,
        internal_npm_dependencies_exclusive: &npm_pack_archive_dependencies
            .keys()
            .map(String::as_str)
            .collect::<Vec<_>>(),
    }
    .render()
    .expect("Unable to render makefile template");

    fs::write(package_directory.join(output_file), makefile_contents)
        .expect("Unable to write makefile");

    Ok(())
}