generated from salesforcecli/plugin-template
-
Notifications
You must be signed in to change notification settings - Fork 2
/
smoke.ts
141 lines (121 loc) · 5.14 KB
/
smoke.ts
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
/*
* Copyright (c) 2020, salesforce.com, inc.
* All rights reserved.
* Licensed under the BSD 3-Clause license.
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
*/
/* eslint-disable no-await-in-loop */
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { exec as execSync } from 'node:child_process';
import { promisify } from 'node:util';
import chalk from 'chalk';
import { Flags, SfCommand } from '@salesforce/sf-plugins-core';
import { Messages, SfError } from '@salesforce/core';
import { Duration, parseJson, ThrottledPromiseAll } from '@salesforce/kit';
import { Interfaces } from '@oclif/core';
import { PackageJson } from '../../../package.js';
import { testJITInstall } from '../../../jit.js';
const exec = promisify(execSync);
Messages.importMessagesDirectoryFromMetaUrl(import.meta.url);
const messages = Messages.loadMessages('@salesforce/plugin-release-management', 'cli.tarballs.smoke');
export default class SmokeTest extends SfCommand<void> {
public static readonly summary = messages.getMessage('description');
public static readonly description = messages.getMessage('description');
public static readonly examples = messages.getMessages('examples');
public static readonly flags = {
verbose: Flags.boolean({
summary: messages.getMessage('flags.verbose.summary'),
}),
};
private flags!: Interfaces.InferredFlags<typeof SmokeTest.flags>;
public async run(): Promise<void> {
this.flags = (await this.parse(SmokeTest)).flags;
await this.smokeTest(path.join('tmp', 'sf', 'bin', 'sf'));
}
private async smokeTest(executable: string): Promise<void> {
await Promise.all([
this.execute(executable, '--version'),
this.execute(executable, '--help'),
this.execute(executable, 'plugins --core'),
this.testInstall(executable, '@salesforce/plugin-settings', 'latest'),
]);
// This tests JIT installs on the generated tarball
// The cli/jit/install/test.ts command tests against a "local" version (e.g. npm install)
// If this test continues to be flakey, it could be removed
await this.testJITInstall(executable);
await this.initializeAllCommands(executable);
}
private async testJITInstall(executable: string): Promise<void> {
await testJITInstall({
jsonEnabled: this.jsonEnabled(),
executable,
manifestPath: path.join('tmp', 'sf', 'oclif.manifest.json'),
});
}
private async testInstall(executable: string, plugin: string, tag?: string): Promise<void> {
await this.execute(executable, `plugins:install ${plugin}${tag ? `@${tag}` : ''}`);
await this.verifyInstall(plugin, executable);
}
private async verifyInstall(plugin: string, executable: string, silent = false): Promise<boolean> {
const fileData = await fs.promises.readFile(
path.join(os.homedir(), '.local', 'share', path.basename(executable), 'package.json'),
'utf-8'
);
const packageJson = parseJson(fileData) as PackageJson;
if (!packageJson.dependencies?.[plugin]) {
if (silent) {
return false;
} else {
throw new SfError(`Failed to install ${plugin}\n`);
}
} else if (!silent) {
this.log('✅ ', chalk.green(`Verified installation of ${plugin}\n`));
return true;
}
return true;
}
private async initializeAllCommands(executable: string): Promise<void> {
this.styledHeader("Initializing help for all 'sf' commands");
// Ran into memory issues when running all commands at once. Now we run them in batches of 10.
const throttledPromise = new ThrottledPromiseAll<string, string | void>({
concurrency: 10,
timeout: Duration.minutes(10),
});
const allCommands = await this.getAllCommands(executable);
const executePromise = async (command: string): Promise<string | void> =>
this.flags.verbose
? this.execute(executable, `${command} --help`)
: this.nonVerboseCommandExecution(executable, command);
throttledPromise.add(allCommands, executePromise);
await throttledPromise.all();
}
private async getAllCommands(executable: string): Promise<string[]> {
const commandsJson = JSON.parse(await this.execute(executable, 'commands --json', true)) as Array<{ id: string }>;
return commandsJson.map((c) => c.id);
}
private async nonVerboseCommandExecution(executable: string, command: string): Promise<void> {
try {
await this.execute(executable, `${command} --help`, true);
this.log(`${executable} ${command} --help ${chalk.green('PASSED')}`);
} catch (err) {
this.log(`${executable} ${command} --help ${chalk.red('FAILED')}`);
throw err;
}
}
private async execute(executable: string, args: string, silent = false): Promise<string> {
const command = `${executable} ${args}`;
try {
const { stdout } = await exec(command, { maxBuffer: 1024 * 1024 * 100 });
if (!silent) {
this.styledHeader(command);
this.log(stdout);
}
return stdout;
} catch (e) {
const err = e as Error;
throw new SfError(`Failed: ${command}.\n ${err.message}`, 'SMOKE_TEST_FAILURE', [], err);
}
}
}