• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In
Build has been canceled!

rokucommunity / ropm / #1310

26 Aug 2025 07:13PM UTC coverage: 90.193%. Remained the same
#1310

push

web-flow
Merge 894f56d99 into 8145979b9

666 of 787 branches covered (84.63%)

Branch coverage included in aggregate %.

14 of 16 new or added lines in 2 files covered. (87.5%)

3 existing lines in 2 files now uncovered.

686 of 712 relevant lines covered (96.35%)

68.69 hits per line

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

82.76
/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
24✔
13
    ) {
14

15
    }
16

17
    private hostPackageJson?: RopmPackageJson;
18

19
    public logger = util.createLogger();
24✔
20

21
    private moduleManager = new ModuleManager({ logger: this.logger });
24✔
22

23

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

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

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

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

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

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

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

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

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

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

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

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

114
    /**
115
     * Get the list of prod dependencies from npm.
116
     * This is run sync because it should run as fast as possible
117
     * and won't be run in ~parallel.
118
     */
119
    public getProdDependencies() {
120
        if (fsExtra.pathExistsSync(this.cwd) === false) {
25!
121
            throw new Error(`"${this.cwd}" does not exist`);
×
122
        }
123
        let stdout: string;
124
        try {
25✔
125
            const npmLs = `npm ls --parseable --omit=dev --omit=optional --depth=Infinity`;
25✔
126
            this.logger.debug(`executing command: ${npmLs}`);
25✔
127

128
            stdout = childProcess.execSync(npmLs, {
25✔
129
                cwd: this.cwd
130
            }).toString();
131

132
        } catch (e) {
UNCOV
133
            stdout = (e as any).stdout.toString();
×
134

135
            // do not throw error, just log a warning
136
            // there are a lot of edge cases where npm ls errors don't pose any actual roadblock for ropm packages
137

NEW
138
            this.logger.warn([
×
139
                'Encountered an error while retrieving the prod dependencies from npm-ls. Attempting to proceed anyways. You can review the error below:\n',
140
                (e as any).message
141
            ].join('\n'));
142
        }
143

144
        return stdout.trim().split(/\r?\n/);
25✔
145
    }
146
}
147

148
export interface InstallCommandArgs extends CommandArgs {
149
    /**
150
     * The list of packages that should be installed
151
     */
152
    packages?: string[];
153
    /**
154
     * Dependencies installation location.
155
     * By default the setting from package.json is imported out-of-the-box, but if rootDir is passed here,
156
     * it will override the value from package.json.
157
     */
158
    rootDir?: string;
159
}
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