@gatling.io/cli 3.11.7 → 3.13.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +8 -6
- package/polyfills/crypto.js +87 -0
- package/polyfills/global.js +13 -0
- package/src/bundle/index.ts +43 -0
- package/src/bundle/polyfill.ts +91 -0
- package/src/commands/build.ts +34 -0
- package/src/commands/enterpriseDeploy.ts +97 -0
- package/src/commands/enterprisePackage.ts +45 -0
- package/src/commands/enterpriseStart.ts +122 -0
- package/src/commands/index.ts +25 -0
- package/src/commands/install.ts +19 -0
- package/src/commands/options.ts +308 -0
- package/src/commands/recorder.ts +41 -0
- package/src/commands/run.ts +82 -0
- package/src/commands/runOnly.ts +56 -0
- package/src/dependencies/coursier.ts +84 -0
- package/src/dependencies/download.ts +11 -0
- package/src/dependencies/graalVm.ts +74 -0
- package/src/dependencies/index.ts +45 -0
- package/src/dependencies/os.ts +24 -0
- package/src/dependencies/versions.ts +15 -0
- package/src/enterprise.ts +227 -0
- package/src/index.ts +5 -0
- package/src/java.ts +48 -0
- package/src/log.ts +19 -0
- package/src/readline.ts +39 -0
- package/src/run.ts +67 -0
- package/src/simulations.ts +29 -0
- package/target/{bundle.d.ts → bundle/index.d.ts} +2 -1
- package/target/{bundle.js → bundle/index.js} +7 -1
- package/target/bundle/polyfill.d.ts +2 -0
- package/target/bundle/polyfill.js +83 -0
- package/target/commands/build.d.ts +3 -0
- package/target/commands/build.js +22 -0
- package/target/commands/enterpriseDeploy.d.ts +3 -0
- package/target/commands/enterpriseDeploy.js +64 -0
- package/target/commands/enterprisePackage.d.ts +3 -0
- package/target/commands/enterprisePackage.js +28 -0
- package/target/commands/enterpriseStart.d.ts +3 -0
- package/target/commands/enterpriseStart.js +80 -0
- package/target/commands/index.d.ts +2 -0
- package/target/commands/index.js +28 -0
- package/target/commands/install.d.ts +3 -0
- package/target/commands/install.js +18 -0
- package/target/commands/options.d.ts +50 -0
- package/target/commands/options.js +214 -0
- package/target/commands/recorder.d.ts +3 -0
- package/target/commands/recorder.js +28 -0
- package/target/commands/run.d.ts +3 -0
- package/target/commands/run.js +53 -0
- package/target/commands/runOnly.d.ts +3 -0
- package/target/commands/runOnly.js +37 -0
- package/target/dependencies/coursier.d.ts +1 -1
- package/target/dependencies/coursier.js +11 -6
- package/target/dependencies/index.d.ts +2 -0
- package/target/dependencies/index.js +4 -2
- package/target/dependencies/versions.d.ts +3 -0
- package/target/dependencies/versions.js +9 -6
- package/target/enterprise.d.ts +2 -1
- package/target/enterprise.js +7 -6
- package/target/index.js +2 -337
- package/target/run.js +4 -2
- package/tsconfig.json +18 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import fs from "fs/promises";
|
|
2
|
+
|
|
3
|
+
import { installCoursier, resolveGatlingJsDependencies, resolveRecorderDependencies } from "./coursier";
|
|
4
|
+
import { installGraalVm } from "./graalVm";
|
|
5
|
+
|
|
6
|
+
export { versions } from "./versions";
|
|
7
|
+
|
|
8
|
+
export interface DependenciesOptions {
|
|
9
|
+
gatlingHome: string;
|
|
10
|
+
postman?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface ResolvedDependencies {
|
|
14
|
+
graalvmHome: string;
|
|
15
|
+
coursierBinary: string;
|
|
16
|
+
jvmClasspath: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const installGatlingJs = async (options: DependenciesOptions): Promise<ResolvedDependencies> => {
|
|
20
|
+
const downloadDir = `${options.gatlingHome}/tmp/download`;
|
|
21
|
+
await fs.mkdir(downloadDir, { recursive: true });
|
|
22
|
+
|
|
23
|
+
const graalvmHomePath = await installGraalVm(options.gatlingHome, downloadDir);
|
|
24
|
+
const coursierPath = await installCoursier(options.gatlingHome, downloadDir);
|
|
25
|
+
const classpath = await resolveGatlingJsDependencies(coursierPath, graalvmHomePath, options.postman);
|
|
26
|
+
return {
|
|
27
|
+
graalvmHome: graalvmHomePath,
|
|
28
|
+
coursierBinary: coursierPath,
|
|
29
|
+
jvmClasspath: classpath.trim()
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export const installRecorder = async (options: DependenciesOptions): Promise<ResolvedDependencies> => {
|
|
34
|
+
const downloadDir = `${options.gatlingHome}/tmp/download`;
|
|
35
|
+
await fs.mkdir(downloadDir, { recursive: true });
|
|
36
|
+
|
|
37
|
+
const graalvmHomePath = await installGraalVm(options.gatlingHome, downloadDir);
|
|
38
|
+
const coursierPath = await installCoursier(options.gatlingHome, downloadDir);
|
|
39
|
+
const classpath = await resolveRecorderDependencies(coursierPath, graalvmHomePath);
|
|
40
|
+
return {
|
|
41
|
+
graalvmHome: graalvmHomePath,
|
|
42
|
+
coursierBinary: coursierPath,
|
|
43
|
+
jvmClasspath: classpath.trim()
|
|
44
|
+
};
|
|
45
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import os from "os";
|
|
2
|
+
|
|
3
|
+
const resolveOsType = () => {
|
|
4
|
+
const t = os.type();
|
|
5
|
+
if (t !== "Darwin" && t !== "Linux" && t !== "Windows_NT") {
|
|
6
|
+
throw Error(`Gatling JS does not support the Operating System '${t}'`);
|
|
7
|
+
}
|
|
8
|
+
return t;
|
|
9
|
+
};
|
|
10
|
+
export const osType: "Darwin" | "Linux" | "Windows_NT" = resolveOsType();
|
|
11
|
+
|
|
12
|
+
const resolveOsArch = () => {
|
|
13
|
+
const a = os.arch();
|
|
14
|
+
if (a !== "x64" && a !== "arm64") {
|
|
15
|
+
throw Error(`Gatling JS does not support the architecture '${a}'`);
|
|
16
|
+
}
|
|
17
|
+
if (osType === "Windows_NT" && a === "arm64") {
|
|
18
|
+
// GraalVM distribution not available for Windows on ARM
|
|
19
|
+
// TODO see if we can recommend a solution
|
|
20
|
+
throw Error(`Gatling JS does not support Windows on the ARM architecture`);
|
|
21
|
+
}
|
|
22
|
+
return a;
|
|
23
|
+
};
|
|
24
|
+
export const osArch: "x64" | "arm64" = resolveOsArch();
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import archiver from "archiver";
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import { pipeline } from "stream/promises";
|
|
4
|
+
import { constants as zConstants } from "zlib";
|
|
5
|
+
|
|
6
|
+
import { versions } from "./dependencies";
|
|
7
|
+
import { RunJavaProcessOptions, runJavaProcess } from "./java";
|
|
8
|
+
import { logger } from "./log";
|
|
9
|
+
import { SimulationFile } from "./simulations";
|
|
10
|
+
|
|
11
|
+
export interface EnterprisePackageOptions {
|
|
12
|
+
bundleFile: string;
|
|
13
|
+
resourcesFolder: string;
|
|
14
|
+
packageFile: string;
|
|
15
|
+
simulations: SimulationFile[];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const enterprisePackage = async (options: EnterprisePackageOptions): Promise<void> => {
|
|
19
|
+
logger.info(`Packaging a Gatling simulation with options:
|
|
20
|
+
- bundleFile: ${options.bundleFile}
|
|
21
|
+
- packageFile: ${options.packageFile}`);
|
|
22
|
+
|
|
23
|
+
const manifest = generateManifest(options.simulations.map((s) => s.name));
|
|
24
|
+
|
|
25
|
+
const output = fs.createWriteStream(options.packageFile);
|
|
26
|
+
|
|
27
|
+
const archive = archiver("zip", {
|
|
28
|
+
zlib: { level: zConstants.Z_MAX_LEVEL }
|
|
29
|
+
});
|
|
30
|
+
archive.on("warning", (err) => {
|
|
31
|
+
// The pipeline will rethrow errors but not warnings. We don't want to ignore warnings from the archiver, because
|
|
32
|
+
// they include things like 'no such file or directory'.
|
|
33
|
+
throw err;
|
|
34
|
+
});
|
|
35
|
+
archive.file(options.bundleFile, { name: "bundle.js" });
|
|
36
|
+
archive.append(Buffer.from(manifest), { name: "META-INF/MANIFEST.MF" });
|
|
37
|
+
archive.directory(options.resourcesFolder + "/", false);
|
|
38
|
+
archive.finalize();
|
|
39
|
+
|
|
40
|
+
await pipeline(archive, output);
|
|
41
|
+
|
|
42
|
+
logger.info(`Package for Gatling Enterprise created at ${options.packageFile}`);
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const generateManifest = (simulationNames: string[]) => {
|
|
46
|
+
const utf8Encoder = new TextEncoder();
|
|
47
|
+
const eol = utf8Encoder.encode("\n");
|
|
48
|
+
const continuation = utf8Encoder.encode("\n ");
|
|
49
|
+
const lines = [
|
|
50
|
+
"Manifest-Version: 1.0",
|
|
51
|
+
"Specification-Vendor: GatlingCorp",
|
|
52
|
+
"Gatling-Context: js",
|
|
53
|
+
`Gatling-Version: ${versions.gatling.core}`,
|
|
54
|
+
"Gatling-Packager: js-cli",
|
|
55
|
+
`Gatling-Packager-Version: ${versions.gatling.jsAdapter}`,
|
|
56
|
+
`Gatling-Simulations: ${simulationNames.join(",")}`,
|
|
57
|
+
`Java-Version: ${versions.java.compilerRelease}`
|
|
58
|
+
];
|
|
59
|
+
const pkg = getPackageNameAndVersion();
|
|
60
|
+
lines.push(`Implementation-Title: ${pkg.name}`);
|
|
61
|
+
if (pkg.version !== undefined) {
|
|
62
|
+
lines.push(`Implementation-Version: ${pkg.version}`);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
let totalLength = 0;
|
|
66
|
+
const buffer: Uint8Array[] = [];
|
|
67
|
+
for (const line of lines) {
|
|
68
|
+
let lineLength = 0;
|
|
69
|
+
for (const char of line) {
|
|
70
|
+
const bytes = utf8Encoder.encode(char);
|
|
71
|
+
const byteLength = bytes.byteLength;
|
|
72
|
+
if (lineLength + byteLength > 71) {
|
|
73
|
+
buffer.push(continuation);
|
|
74
|
+
buffer.push(bytes);
|
|
75
|
+
// reset length for the new line (with +1 for leading space)
|
|
76
|
+
lineLength = byteLength + 1;
|
|
77
|
+
totalLength += byteLength + 2;
|
|
78
|
+
} else {
|
|
79
|
+
buffer.push(bytes);
|
|
80
|
+
lineLength += byteLength;
|
|
81
|
+
totalLength += byteLength;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
buffer.push(eol);
|
|
85
|
+
totalLength += 1;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const manifest = new Uint8Array(totalLength);
|
|
89
|
+
let cursor = 0;
|
|
90
|
+
for (const elt of buffer) {
|
|
91
|
+
manifest.set(elt, cursor);
|
|
92
|
+
cursor += elt.byteLength;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return manifest;
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const getPackageNameAndVersion = (): { name: string; version?: string } => {
|
|
99
|
+
// npm_package_* env vars are available when launching CLI with npx
|
|
100
|
+
let name = process.env.npm_package_name;
|
|
101
|
+
let version = process.env.npm_package_version;
|
|
102
|
+
// otherwise, try to read from package.json file
|
|
103
|
+
if (name === undefined || version === undefined) {
|
|
104
|
+
if (!fs.existsSync("package.json")) {
|
|
105
|
+
throw Error("package.json not found");
|
|
106
|
+
}
|
|
107
|
+
const pkg = JSON.parse(fs.readFileSync("package.json", "utf-8"));
|
|
108
|
+
if (name === undefined) {
|
|
109
|
+
if (typeof pkg.name === "string") {
|
|
110
|
+
name = pkg.name;
|
|
111
|
+
} else {
|
|
112
|
+
throw Error("package.json does not contain a valid package name");
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
if (version === undefined && typeof pkg.version === "string") {
|
|
116
|
+
version = pkg.version;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return { name: name as string, version: version };
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
export interface EnterprisePluginOptions extends RunJavaProcessOptions {
|
|
123
|
+
bundleFile: string;
|
|
124
|
+
resourcesFolder: string;
|
|
125
|
+
resultsFolder: string;
|
|
126
|
+
// Base
|
|
127
|
+
apiUrl: string;
|
|
128
|
+
webAppUrl: string;
|
|
129
|
+
apiToken?: string;
|
|
130
|
+
// Plugin configuration
|
|
131
|
+
controlPlaneUrl?: string;
|
|
132
|
+
nonInteractive: boolean;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const javaArgsFromPluginOptions = (options: EnterprisePluginOptions) => {
|
|
136
|
+
const javaArgs: string[] = [];
|
|
137
|
+
|
|
138
|
+
// Base
|
|
139
|
+
javaArgs.push(`-Dgatling.enterprise.apiUrl=${options.apiUrl}`);
|
|
140
|
+
javaArgs.push(`-Dgatling.enterprise.webAppUrl=${options.webAppUrl}`);
|
|
141
|
+
if (options.apiToken !== undefined) {
|
|
142
|
+
javaArgs.push(`-Dgatling.enterprise.apiToken=${options.apiToken}`);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Plugin configuration
|
|
146
|
+
if (options.controlPlaneUrl !== undefined) {
|
|
147
|
+
javaArgs.push(`-Dgatling.enterprise.controlPlaneUrl=${options.controlPlaneUrl}`);
|
|
148
|
+
}
|
|
149
|
+
javaArgs.push("-Dgatling.enterprise.buildTool=js-cli");
|
|
150
|
+
javaArgs.push(`-Dgatling.enterprise.pluginVersion=${versions.gatling.jsAdapter}`);
|
|
151
|
+
|
|
152
|
+
if (options.nonInteractive) {
|
|
153
|
+
javaArgs.push(`-Dgatling.enterprise.batchMode=true`);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return javaArgs;
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
export interface EnterpriseDeployOptions extends EnterprisePluginOptions {
|
|
160
|
+
// Descriptor file
|
|
161
|
+
packageDescriptorFilename: string;
|
|
162
|
+
// Deployment info
|
|
163
|
+
packageFile: string;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const javaArgsFromDeployOptions = (options: EnterpriseDeployOptions) => {
|
|
167
|
+
const javaArgs = javaArgsFromPluginOptions(options);
|
|
168
|
+
|
|
169
|
+
// Descriptor file
|
|
170
|
+
javaArgs.push(`-Dgatling.enterprise.baseDirectory=${process.cwd()}`);
|
|
171
|
+
javaArgs.push(`-Dgatling.enterprise.packageDescriptorFilename=${options.packageDescriptorFilename}`);
|
|
172
|
+
|
|
173
|
+
// Deployment info
|
|
174
|
+
javaArgs.push(`-Dgatling.enterprise.packageFile=${options.packageFile}`);
|
|
175
|
+
javaArgs.push(`-Dgatling.enterprise.artifactId=${getPackageNameAndVersion().name}`);
|
|
176
|
+
|
|
177
|
+
return javaArgs;
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
export const enterpriseDeploy = async (options: EnterpriseDeployOptions): Promise<void> => {
|
|
181
|
+
const additionalClasspathElements = [options.resourcesFolder];
|
|
182
|
+
const javaArgs = javaArgsFromDeployOptions(options);
|
|
183
|
+
|
|
184
|
+
if (process.env["DEBUG"] === "true") {
|
|
185
|
+
logger.debug("Java arguments:");
|
|
186
|
+
for (let i = 0; i < javaArgs.length; i++) {
|
|
187
|
+
logger.debug(" " + javaArgs[i]);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return runJavaProcess(options, "io.gatling.plugin.cli.EnterpriseDeploy", additionalClasspathElements, javaArgs, []);
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
export interface EnterpriseStartOptions extends EnterpriseDeployOptions {
|
|
195
|
+
enterpriseSimulation?: string;
|
|
196
|
+
runTitle?: string;
|
|
197
|
+
runDescription?: string;
|
|
198
|
+
waitForRunEnd?: boolean;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export const enterpriseStart = async (options: EnterpriseStartOptions): Promise<void> => {
|
|
202
|
+
const additionalClasspathElements = [options.resourcesFolder];
|
|
203
|
+
const javaArgs = javaArgsFromDeployOptions(options);
|
|
204
|
+
|
|
205
|
+
// Start
|
|
206
|
+
if (options.enterpriseSimulation !== undefined) {
|
|
207
|
+
javaArgs.push(`-Dgatling.enterprise.simulationName=${options.enterpriseSimulation}`);
|
|
208
|
+
}
|
|
209
|
+
if (options.runTitle !== undefined) {
|
|
210
|
+
javaArgs.push(`-Dgatling.enterprise.runTitle=${options.runTitle}`);
|
|
211
|
+
}
|
|
212
|
+
if (options.runDescription !== undefined) {
|
|
213
|
+
javaArgs.push(`-Dgatling.enterprise.runDescription=${options.runDescription}`);
|
|
214
|
+
}
|
|
215
|
+
if (options.waitForRunEnd) {
|
|
216
|
+
javaArgs.push("-Dgatling.enterprise.waitForRunEnd=true");
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (process.env["DEBUG"] === "true") {
|
|
220
|
+
logger.debug("Java arguments:");
|
|
221
|
+
for (let i = 0; i < javaArgs.length; i++) {
|
|
222
|
+
logger.debug(" " + javaArgs[i]);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return runJavaProcess(options, "io.gatling.plugin.cli.EnterpriseStart", additionalClasspathElements, javaArgs, []);
|
|
227
|
+
};
|
package/src/index.ts
ADDED
package/src/java.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { spawn } from "child_process";
|
|
2
|
+
|
|
3
|
+
import { osType } from "./dependencies/os";
|
|
4
|
+
import { logger } from "./log";
|
|
5
|
+
|
|
6
|
+
export interface RunJavaProcessOptions {
|
|
7
|
+
graalvmHome: string;
|
|
8
|
+
jvmClasspath: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const runJavaProcess = (
|
|
12
|
+
options: RunJavaProcessOptions,
|
|
13
|
+
mainClass: string,
|
|
14
|
+
additionalClasspathElements: string[],
|
|
15
|
+
javaArgs: string[],
|
|
16
|
+
mainClassArgs: string[]
|
|
17
|
+
): Promise<void> => {
|
|
18
|
+
const command = `${options.graalvmHome}/bin/java`;
|
|
19
|
+
const classpathSeparator = osType === "Windows_NT" ? ";" : ":";
|
|
20
|
+
const classpath = [...additionalClasspathElements, options.jvmClasspath].join(classpathSeparator);
|
|
21
|
+
const allArgs = [
|
|
22
|
+
"-server",
|
|
23
|
+
"-XX:+HeapDumpOnOutOfMemoryError",
|
|
24
|
+
"-XX:MaxInlineLevel=20",
|
|
25
|
+
"-XX:MaxTrivialSize=12",
|
|
26
|
+
"-classpath",
|
|
27
|
+
classpath,
|
|
28
|
+
...javaArgs,
|
|
29
|
+
mainClass,
|
|
30
|
+
...mainClassArgs
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
const spawned = spawn(command, allArgs, {
|
|
34
|
+
env: process.env,
|
|
35
|
+
stdio: [process.stdin, process.stdout, process.stderr]
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
return new Promise((resolve, reject) => {
|
|
39
|
+
spawned.on("error", (error) => logger.error("Failed to run Gatling process: " + error.toString()));
|
|
40
|
+
spawned.on("close", (code) => {
|
|
41
|
+
if (code === 0) {
|
|
42
|
+
resolve();
|
|
43
|
+
} else {
|
|
44
|
+
reject(Error("Gatling process finished with code " + code));
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
};
|
package/src/log.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
const debug: (message: string) => void =
|
|
2
|
+
// On Node.js, console.debug is just an alias to console.log, so we handle debug level ourselves
|
|
3
|
+
process.env["DEBUG"] === "true" ? console.debug : () => {};
|
|
4
|
+
|
|
5
|
+
const info: (message: string) => void = console.info;
|
|
6
|
+
|
|
7
|
+
const error: (message: string) => void = console.error;
|
|
8
|
+
|
|
9
|
+
export interface Logger {
|
|
10
|
+
debug: (message: string) => void;
|
|
11
|
+
info: (message: string) => void;
|
|
12
|
+
error: (message: string) => void;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const logger: Logger = {
|
|
16
|
+
debug,
|
|
17
|
+
info,
|
|
18
|
+
error
|
|
19
|
+
};
|
package/src/readline.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { keyInSelect, BasicOptions } from "readline-sync";
|
|
2
|
+
|
|
3
|
+
// Inspired by https://github.com/anseki/readline-sync/issues/60#issuecomment-324533678
|
|
4
|
+
// Pagination avoids very long lists to scroll though, as well as the hard limit at 35 items for keyInSelect
|
|
5
|
+
export const keyInSelectPaginated = (items: string[], query?: any, options?: BasicOptions | undefined): number => {
|
|
6
|
+
if (items.length === 0) {
|
|
7
|
+
return -1;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const maxItemsPerPage = 10;
|
|
11
|
+
const maxPageIndex = Math.ceil(items.length / maxItemsPerPage) - 1;
|
|
12
|
+
|
|
13
|
+
let pageIndex = 0;
|
|
14
|
+
while (true) {
|
|
15
|
+
const pageItems = [];
|
|
16
|
+
let indexPrev = -1;
|
|
17
|
+
let indexNext = -1;
|
|
18
|
+
if (pageIndex > 0) {
|
|
19
|
+
pageItems.push(`(PREVIOUS ${maxItemsPerPage} items)`);
|
|
20
|
+
indexPrev = pageItems.length - 1;
|
|
21
|
+
}
|
|
22
|
+
pageItems.push(...items.slice(pageIndex * maxItemsPerPage, (pageIndex + 1) * maxItemsPerPage));
|
|
23
|
+
if (pageIndex < maxPageIndex) {
|
|
24
|
+
pageItems.push(
|
|
25
|
+
`(NEXT ${pageIndex < maxPageIndex - 1 ? maxItemsPerPage : items.length - maxItemsPerPage * (pageIndex + 1)} item(s))`
|
|
26
|
+
);
|
|
27
|
+
indexNext = pageItems.length - 1;
|
|
28
|
+
}
|
|
29
|
+
console.log("\x1B[2J"); // clear screen
|
|
30
|
+
const index = keyInSelect(pageItems, query, options);
|
|
31
|
+
if (indexPrev !== -1 && index === indexPrev) {
|
|
32
|
+
pageIndex--;
|
|
33
|
+
} else if (indexNext !== -1 && index === indexNext) {
|
|
34
|
+
pageIndex++;
|
|
35
|
+
} else {
|
|
36
|
+
return index === -1 ? index : index + pageIndex * maxItemsPerPage - (indexPrev === -1 ? 0 : 1);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
};
|
package/src/run.ts
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { logger } from "./log";
|
|
2
|
+
import { versions } from "./dependencies";
|
|
3
|
+
import { RunJavaProcessOptions, runJavaProcess } from "./java";
|
|
4
|
+
|
|
5
|
+
export interface RunSimulationOptions extends RunJavaProcessOptions {
|
|
6
|
+
simulation: string;
|
|
7
|
+
bundleFile: string;
|
|
8
|
+
resourcesFolder: string;
|
|
9
|
+
resultsFolder: string;
|
|
10
|
+
memory?: number;
|
|
11
|
+
runParameters: Record<string, string>;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface RunRecorderOptions extends RunJavaProcessOptions {
|
|
15
|
+
sourcesFolder: string;
|
|
16
|
+
typescript: boolean;
|
|
17
|
+
resourcesFolder: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const runSimulation = async (options: RunSimulationOptions): Promise<void> => {
|
|
21
|
+
logger.info(`Running a Gatling simulation with options:
|
|
22
|
+
- simulation: ${options.simulation}
|
|
23
|
+
- bundleFile: ${options.bundleFile}
|
|
24
|
+
- resourcesFolder: ${options.resourcesFolder}
|
|
25
|
+
- resultsFolder: ${options.resultsFolder}`);
|
|
26
|
+
|
|
27
|
+
const additionalClasspathElements = [options.resourcesFolder];
|
|
28
|
+
const jitTuningArgs = ["-XX:JVMCINativeLibraryThreadFraction=0.8", "-Dgraal.MethodInlineBailoutLimit=500"];
|
|
29
|
+
const memoryArgs = options.memory !== undefined ? [`-Xms${options.memory}M`, `-Xmx${options.memory}M`] : [];
|
|
30
|
+
const javaArgs = [
|
|
31
|
+
...Object.entries(options.runParameters).map(([key, value]) => `-D${key}=${value}`),
|
|
32
|
+
`-Dgatling.js.bundle.filePath=${options.bundleFile}`,
|
|
33
|
+
`-Dgatling.js.simulation=${options.simulation}`,
|
|
34
|
+
...jitTuningArgs,
|
|
35
|
+
...memoryArgs
|
|
36
|
+
];
|
|
37
|
+
const simulationArgs = [
|
|
38
|
+
"--results-folder",
|
|
39
|
+
options.resultsFolder,
|
|
40
|
+
"--simulation",
|
|
41
|
+
"io.gatling.js.JsSimulation",
|
|
42
|
+
"--launcher",
|
|
43
|
+
"gatling-js-cli",
|
|
44
|
+
"--build-tool-version",
|
|
45
|
+
versions.gatling.jsAdapter
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
return runJavaProcess(options, "io.gatling.app.Gatling", additionalClasspathElements, javaArgs, simulationArgs);
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export const runRecorder = async (options: RunRecorderOptions): Promise<void> => {
|
|
52
|
+
logger.info(`Running the Gatling Recorder with options:
|
|
53
|
+
- sourcesFolder: ${options.sourcesFolder}
|
|
54
|
+
- resourcesFolder: ${options.resourcesFolder}
|
|
55
|
+
- typescript: ${options.typescript}`);
|
|
56
|
+
|
|
57
|
+
const recorderArgs = [
|
|
58
|
+
"--simulations-folder",
|
|
59
|
+
options.sourcesFolder,
|
|
60
|
+
"--resources-folder",
|
|
61
|
+
options.resourcesFolder,
|
|
62
|
+
"--format",
|
|
63
|
+
options.typescript ? "typescript" : "javascript"
|
|
64
|
+
];
|
|
65
|
+
|
|
66
|
+
return runJavaProcess(options, "io.gatling.recorder.GatlingRecorder", [], [], recorderArgs);
|
|
67
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import fs from "fs/promises";
|
|
2
|
+
|
|
3
|
+
export interface SimulationFile {
|
|
4
|
+
path: string;
|
|
5
|
+
name: string;
|
|
6
|
+
type: "javascript" | "typescript";
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const findSimulations = async (sourcesFolder: string): Promise<Array<SimulationFile>> => {
|
|
10
|
+
const children = await fs.readdir(sourcesFolder, { recursive: false });
|
|
11
|
+
const simulations = children
|
|
12
|
+
.filter((path) => path.endsWith(".gatling.js") || path.endsWith(".gatling.ts"))
|
|
13
|
+
.map(
|
|
14
|
+
(path): SimulationFile => ({
|
|
15
|
+
path,
|
|
16
|
+
name: path.slice(0, -11),
|
|
17
|
+
type: path.endsWith(".ts") ? "typescript" : "javascript"
|
|
18
|
+
})
|
|
19
|
+
);
|
|
20
|
+
const duplicates = simulations.filter(
|
|
21
|
+
(value, index) => simulations.findIndex((other) => other.name === value.name) !== index
|
|
22
|
+
);
|
|
23
|
+
if (duplicates.length > 0) {
|
|
24
|
+
throw Error(
|
|
25
|
+
`Found ambiguous simulation name(s) ${duplicates.map((s) => s.name)}: found as both *.gatling.js and *.gatling.ts file(s)`
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
return simulations;
|
|
29
|
+
};
|
|
@@ -29,7 +29,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
29
29
|
exports.bundle = void 0;
|
|
30
30
|
const esbuild = __importStar(require("esbuild"));
|
|
31
31
|
const esbuild_plugin_tsc_1 = __importDefault(require("esbuild-plugin-tsc"));
|
|
32
|
-
const
|
|
32
|
+
const polyfill_1 = require("./polyfill");
|
|
33
|
+
const log_1 = require("../log");
|
|
33
34
|
const bundle = async (options) => {
|
|
34
35
|
log_1.logger.info(`Bundling a Gatling simulation with options:
|
|
35
36
|
- sourcesFolder: ${options.sourcesFolder}
|
|
@@ -37,12 +38,17 @@ const bundle = async (options) => {
|
|
|
37
38
|
- typescript: ${options.typescript}`);
|
|
38
39
|
const contents = options.simulations.map((s) => `export { default as "${s.name}" } from "./${s.path}";`).join("\n");
|
|
39
40
|
const plugins = options.typescript ? [(0, esbuild_plugin_tsc_1.default)({ force: true })] : [];
|
|
41
|
+
if (options.postman !== undefined) {
|
|
42
|
+
plugins.push((0, polyfill_1.polyfill)());
|
|
43
|
+
}
|
|
40
44
|
await esbuild.build({
|
|
41
45
|
stdin: {
|
|
42
46
|
contents,
|
|
43
47
|
resolveDir: options.sourcesFolder
|
|
44
48
|
},
|
|
45
49
|
outfile: options.bundleFile,
|
|
50
|
+
platform: "neutral",
|
|
51
|
+
mainFields: ["main", "module"],
|
|
46
52
|
bundle: true,
|
|
47
53
|
minify: false,
|
|
48
54
|
sourcemap: true,
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.polyfill = void 0;
|
|
4
|
+
const url_1 = require("url");
|
|
5
|
+
const path_1 = require("path");
|
|
6
|
+
// This is largely inspired by https://github.com/cyco130/esbuild-plugin-polyfill-node
|
|
7
|
+
const polyfill = () => ({
|
|
8
|
+
name: "gatling-js-polyfill",
|
|
9
|
+
setup: async (build) => {
|
|
10
|
+
// modules
|
|
11
|
+
const jspmResolved = await resolveImport(`@jspm/core/nodelibs/fs`);
|
|
12
|
+
build.onResolve({ filter: polyfillsFilter }, async ({ path }) => {
|
|
13
|
+
const [, , moduleName] = path.match(polyfillsFilter);
|
|
14
|
+
const resolved = customPolyfills.find((name) => name === moduleName)
|
|
15
|
+
? (0, path_1.resolve)((0, path_1.dirname)(__filename), `../../polyfills/${moduleName}.js`)
|
|
16
|
+
: (0, path_1.resolve)(jspmResolved, `../../browser/${moduleName}.js`);
|
|
17
|
+
return { path: resolved };
|
|
18
|
+
});
|
|
19
|
+
// Globals
|
|
20
|
+
build.initialOptions.inject = build.initialOptions.inject || [];
|
|
21
|
+
const injectGlobal = (name) => build.initialOptions.inject.push((0, path_1.resolve)((0, path_1.dirname)(__filename), `../../polyfills/${name}.js`));
|
|
22
|
+
injectGlobal("global");
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
exports.polyfill = polyfill;
|
|
26
|
+
const customPolyfills = ["crypto"];
|
|
27
|
+
const jspmPolyfills = ["buffer", "path", "string_decoder"];
|
|
28
|
+
// Other available jspm-core modules:
|
|
29
|
+
// "_stream_duplex"
|
|
30
|
+
// "_stream_passthrough"
|
|
31
|
+
// "_stream_readable"
|
|
32
|
+
// "_stream_transform"
|
|
33
|
+
// "_stream_writable"
|
|
34
|
+
// "assert"
|
|
35
|
+
// "assert/strict"
|
|
36
|
+
// "async_hooks"
|
|
37
|
+
// "child_process"
|
|
38
|
+
// "cluster"
|
|
39
|
+
// "console"
|
|
40
|
+
// "constants"
|
|
41
|
+
// "crypto"
|
|
42
|
+
// "dgram"
|
|
43
|
+
// "diagnostics_channel"
|
|
44
|
+
// "dns"
|
|
45
|
+
// "domain"
|
|
46
|
+
// "events"
|
|
47
|
+
// "fs"
|
|
48
|
+
// "fs/promises"
|
|
49
|
+
// "http"
|
|
50
|
+
// "http2"
|
|
51
|
+
// "https"
|
|
52
|
+
// "module"
|
|
53
|
+
// "net"
|
|
54
|
+
// "os"
|
|
55
|
+
// "perf_hooks"
|
|
56
|
+
// "process"
|
|
57
|
+
// "punycode"
|
|
58
|
+
// "querystring"
|
|
59
|
+
// "readline"
|
|
60
|
+
// "repl"
|
|
61
|
+
// "stream"
|
|
62
|
+
// "sys"
|
|
63
|
+
// "timers"
|
|
64
|
+
// "timers/promises"
|
|
65
|
+
// "tls"
|
|
66
|
+
// "tty"
|
|
67
|
+
// "url"
|
|
68
|
+
// "util"
|
|
69
|
+
// "v8"
|
|
70
|
+
// "vm"
|
|
71
|
+
// "wasi"
|
|
72
|
+
// "worker_threads"
|
|
73
|
+
// "zlib"
|
|
74
|
+
const polyfillsFilter = new RegExp(`^(node:)?(${jspmPolyfills.concat(customPolyfills).join("|")})$`);
|
|
75
|
+
let importMetaResolve;
|
|
76
|
+
const importMetaUrl = (0, url_1.pathToFileURL)(__filename).href;
|
|
77
|
+
const resolveImport = async (specifier) => {
|
|
78
|
+
if (!importMetaResolve) {
|
|
79
|
+
importMetaResolve = (await import("import-meta-resolve")).resolve;
|
|
80
|
+
}
|
|
81
|
+
const resolved = importMetaResolve(specifier, importMetaUrl);
|
|
82
|
+
return (0, url_1.fileURLToPath)(resolved);
|
|
83
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const options_1 = require("./options");
|
|
4
|
+
const simulations_1 = require("../simulations");
|
|
5
|
+
const bundle_1 = require("../bundle");
|
|
6
|
+
exports.default = (program) => {
|
|
7
|
+
program
|
|
8
|
+
.command("build")
|
|
9
|
+
.description("Build Gatling simulations")
|
|
10
|
+
.addOption(options_1.sourcesFolderOption)
|
|
11
|
+
.addOption(options_1.bundleFileOption)
|
|
12
|
+
.addOption(options_1.postmanOption)
|
|
13
|
+
.addOption(options_1.typescriptOption)
|
|
14
|
+
.action(async (options) => {
|
|
15
|
+
const sourcesFolder = (0, options_1.sourcesFolderOptionValue)(options);
|
|
16
|
+
const bundleFile = (0, options_1.bundleFileOptionValue)(options);
|
|
17
|
+
const simulations = await (0, simulations_1.findSimulations)(sourcesFolder);
|
|
18
|
+
const postman = (0, options_1.postmanOptionValueWithDefaults)(options);
|
|
19
|
+
const typescript = (0, options_1.typescriptOptionValueWithDefaults)(options, simulations);
|
|
20
|
+
await (0, bundle_1.bundle)({ sourcesFolder, bundleFile, postman, typescript, simulations });
|
|
21
|
+
});
|
|
22
|
+
};
|