• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

rokucommunity / ropm / #1169

26 Mar 2025 02:30PM UTC coverage: 90.283% (+0.2%) from 90.115%
#1169

Pull #92

chrisdp
Some messaging clean up and consolidated how we create the loggers for consistancy
Pull Request #92: Added log level support

661 of 782 branches covered (84.53%)

Branch coverage included in aggregate %.

44 of 48 new or added lines in 6 files covered. (91.67%)

5 existing lines in 1 file now uncovered.

677 of 700 relevant lines covered (96.71%)

68.89 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

83.9
/src/commands/InstallCommand.ts
1
import type { CommandArgs, RopmPackageJson } from '../util';
2
import { util } from '../util';
1✔
3
import * as path from 'path';
1✔
4
import * as childProcess from 'child_process';
1✔
5
import * as fsExtra from 'fs-extra';
1✔
6
import { InitCommand } from './InitCommand';
1✔
7
import { CleanCommand } from './CleanCommand';
1✔
8
import { ModuleManager } from '../prefixer/ModuleManager';
1✔
9

10
export class InstallCommand {
1✔
11
    constructor(
12
        public args: InstallCommandArgs
25✔
13
    ) {
14

15
    }
16

17
    private hostPackageJson?: RopmPackageJson;
18

19
    public logger = util.createLogger();
25✔
20
    private moduleManager = new ModuleManager({ logger: this.logger });
25✔
21

22

23
    private get hostRootDir() {
24
        const packageJsonRootDir = this.args.rootDir ?? this.hostPackageJson?.ropm?.rootDir;
20!
25
        if (packageJsonRootDir) {
20✔
26
            return path.resolve(this.cwd, packageJsonRootDir);
4✔
27
        } else {
28
            return this.cwd;
16✔
29
        }
30
    }
31

32
    private get cwd() {
33
        if (this.args?.cwd) {
177!
34
            return path.resolve(process.cwd(), this.args?.cwd);
177!
35
        } else {
UNCOV
36
            return process.cwd();
×
37
        }
38
    }
39

40
    public async run(runNpmInstall = true): Promise<void> {
21✔
41
        await this.loadHostPackageJson();
21✔
42
        this.updateLogLevel();
21✔
43
        await this.deleteAllRokuModulesFolders();
21✔
44
        if (runNpmInstall) {
21!
45
            await this.npmInstall();
21✔
46
        }
47
        await this.processModules();
21✔
48
    }
49

50
    /**
51
     * Deletes every roku_modules folder found in the hostRootDir
52
     */
53
    private async deleteAllRokuModulesFolders() {
54
        const cleanCommand = new CleanCommand({
21✔
55
            cwd: this.cwd,
56
            logLevel: this.args.logLevel
57
        });
58
        await cleanCommand.run();
21✔
59
    }
60

61
    /**
62
     * A "host" is the project we are currently operating upon. This method
63
     * finds the package.json file for the current host
64
     */
65
    private async loadHostPackageJson() {
66
        //if the host doesn't currently have a package.json
67
        if (await fsExtra.pathExists(path.resolve(this.cwd, 'package.json')) === false) {
21!
NEW
UNCOV
68
            this.logger.log('Creating package.json');
×
69
            //init package.json for the host
NEW
UNCOV
70
            await new InitCommand({ cwd: this.cwd, force: true, promptForRootDir: true, logLevel: this.args.logLevel }).run();
×
71
        }
72
        this.hostPackageJson = await util.getPackageJson(this.cwd);
21✔
73
    }
74

75
    private updateLogLevel() {
76
        //set the logLevel provided by the RopmOptions
77
        this.logger.logLevel = this.args.logLevel ?? this.hostPackageJson?.ropm?.logLevel ?? 'log';
21!
78
    }
79

80
    private async npmInstall() {
81
        if (await fsExtra.pathExists(this.cwd) === false) {
21!
UNCOV
82
            throw new Error(`"${this.cwd}" does not exist`);
×
83
        }
84
        await util.spawnNpmAsync([
21✔
85
            'i',
86
            ...(this.args.packages ?? [])
63✔
87
        ], {
88
            cwd: this.cwd
89
        });
90
    }
91

92
    /**
93
     * Copy all modules to roku_modules
94
     */
95
    private async processModules() {
96
        const modulePaths = this.getProdDependencies();
21✔
97

98
        //remove the host module from the list (it should always be the first entry)
99
        const hostModulePath = modulePaths.splice(0, 1)[0];
20✔
100
        this.moduleManager.hostDependencies = await util.getModuleDependencies(hostModulePath);
20✔
101

102
        this.moduleManager.hostRootDir = this.hostRootDir;
20✔
103
        this.moduleManager.noprefixNpmAliases = this.hostPackageJson?.ropm?.noprefix ?? [];
20!
104

105
        //copy all of them at once, wait for them all to complete
106
        for (const modulePath of modulePaths) {
20✔
107
            this.moduleManager.addModule(modulePath);
20✔
108
        }
109

110
        await this.moduleManager.process();
20✔
111
    }
112

113
    /**
114
     * Get the list of prod dependencies from npm.
115
     * This is run sync because it should run as fast as possible
116
     * and won't be run in ~parallel.
117
     */
118
    public getProdDependencies() {
119
        if (fsExtra.pathExistsSync(this.cwd) === false) {
26!
UNCOV
120
            throw new Error(`"${this.cwd}" does not exist`);
×
121
        }
122
        let stdout: string;
123
        try {
26✔
124
            stdout = childProcess.execSync('npm ls --parseable --prod --depth=Infinity', {
26✔
125
                cwd: this.cwd
126
            }).toString();
127
        } catch (e) {
128
            stdout = (e as any).stdout.toString();
1✔
129
            const stderr: string = (e as any).stderr.toString();
1✔
130
            //sometimes the unit tests absorb stderr...so as long as we have stdout, assume it's valid (and ignore the stderr)
131
            if (stderr.includes('npm ERR! extraneous:')) {
1!
132
                //ignore errors
133
            } else {
134
                throw new Error('Failed to compute prod dependencies: ' +  (e as any).message);
1✔
135
            }
136
        }
137

138
        return stdout.trim().split(/\r?\n/);
25✔
139
    }
140
}
141

142
export interface InstallCommandArgs extends CommandArgs {
143
    /**
144
     * The list of packages that should be installed
145
     */
146
    packages?: string[];
147
    /**
148
     * Dependencies installation location.
149
     * By default the setting from package.json is imported out-of-the-box, but if rootDir is passed here,
150
     * it will override the value from package.json.
151
     */
152
    rootDir?: string;
153
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc