aboutsummaryrefslogtreecommitdiff
path: root/src/which_npm_client.zig
blob: 86683a368186020bc7ef4b4dc6a958badb6ff58f (plain) (blame)
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
usingnamespace @import("./global.zig");

const which = @import("./which.zig").which;
const std = @import("std");

pub const NPMClient = struct {
    bin: string,
    tag: Tag,

    pub const Tag = enum {
        npm,
        yarn,
        pnpm,
    };

    pub fn isYarnBerry(allocator: *std.mem.Allocator, yarn_path: string) bool {
        var args = [_]string{ yarn_path, "--version" };
        var child_process = std.ChildProcess.init(&args, allocator) catch return true;
        defer child_process.deinit();
        child_process.cwd_dir = std.fs.cwd();
        child_process.expand_arg0 = .no_expand;
        child_process.stdout_behavior = .Pipe;
        child_process.stderr_behavior = .Pipe;
        child_process.spawn() catch return true;
        defer _ = child_process.kill() catch undefined;

        var path_buf: [512]u8 = undefined;
        var path_len = child_process.stdout.?.read(&path_buf) catch return true;

        if (path_len == 0) {
            return true;
        }

        return path_buf[0] != '1';
    }

    pub fn detect(allocator: *std.mem.Allocator, realpath_buf: *[std.fs.MAX_PATH_BYTES]u8, PATH: string, cwd: string, comptime allow_yarn: bool) !?NPMClient {

        // We say:
        // - pnpm if it exists, is the default. its most esoteric, so if you have it installed, you prob want it.
        // - yarn if it exists and it is yarn 1, its the default (yarn 2 or later is not supported)
        // - else npm
        var path_buf: [std.fs.MAX_PATH_BYTES]u8 = undefined;

        const path: [:0]const u8 = brk: {
            if (comptime allow_yarn) {
                break :brk which(
                    &path_buf,
                    PATH,
                    cwd,
                    "pnpm",
                ) orelse which(
                    &path_buf,
                    PATH,
                    cwd,
                    "yarn",
                ) orelse which(
                    &path_buf,
                    PATH,
                    cwd,
                    "npm",
                ) orelse "";
            } else {
                break :brk which(
                    &path_buf,
                    PATH,
                    cwd,
                    "pnpm",
                ) orelse which(
                    &path_buf,
                    PATH,
                    cwd,
                    "npm",
                ) orelse "";
            }
            unreachable;
        };

        var basename = std.fs.path.basename(path);
        if (basename.len == 0) return null;

        if (comptime allow_yarn) {
            if (std.mem.indexOf(u8, basename, "yarn") != null) {
                if (isYarnBerry(allocator, path)) {
                    return try detect(allocator, realpath_buf, PATH, cwd, false);
                }
            }
        }

        var file = std.fs.openFileAbsoluteZ(path, .{ .read = true }) catch return null;
        defer file.close();
        const out_path = std.os.getFdPath(file.handle, realpath_buf) catch return null;

        if (strings.contains(basename, "pnpm")) {
            return NPMClient{ .bin = out_path, .tag = .pnpm };
        }

        if (strings.contains(basename, "yarn")) {
            return NPMClient{ .bin = out_path, .tag = .yarn };
        }

        if (strings.contains(basename, "npm")) {
            return NPMClient{ .bin = out_path, .tag = .npm };
        }

        return null;
    }
};