@gatling.io/cli 0.1.0

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 ADDED
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "@gatling.io/cli",
3
+ "version": "0.1.0",
4
+ "bin": {
5
+ "gatling-js-cli": "target/index.js"
6
+ },
7
+ "main": "target/index.js",
8
+ "types": "target/index.d.ts",
9
+ "dependencies": {
10
+ "axios": "1.6.8",
11
+ "commander": "12.0.0",
12
+ "decompress": "4.2.1",
13
+ "esbuild": "0.20.2",
14
+ "esbuild-plugin-tsc": "0.4.0"
15
+ },
16
+ "devDependencies": {
17
+ "@types/decompress": "4.2.7",
18
+ "@types/node": "20.12.9",
19
+ "prettier": "3.2.5",
20
+ "rimraf": "5.0.5",
21
+ "typescript": "5.4.5"
22
+ },
23
+ "scripts": {
24
+ "clean": "rimraf target results tmp",
25
+ "format": "prettier --write '**/*.ts'",
26
+ "build": "tsc -p . && chmod +x ./target/index.js"
27
+ }
28
+ }
@@ -0,0 +1,6 @@
1
+ export interface BundleOptions {
2
+ entrypointFile: string;
3
+ bundleFile: string;
4
+ typescript: boolean;
5
+ }
6
+ export declare const bundle: (options: BundleOptions) => Promise<void>;
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ var __importDefault = (this && this.__importDefault) || function (mod) {
26
+ return (mod && mod.__esModule) ? mod : { "default": mod };
27
+ };
28
+ Object.defineProperty(exports, "__esModule", { value: true });
29
+ exports.bundle = void 0;
30
+ const esbuild = __importStar(require("esbuild"));
31
+ const esbuild_plugin_tsc_1 = __importDefault(require("esbuild-plugin-tsc"));
32
+ const log_1 = require("./log");
33
+ const bundle = async (options) => {
34
+ log_1.logger.info(`Packaging a Gatling simulation with options:
35
+ - entrypointFile: ${options.entrypointFile}
36
+ - bundleFile: ${options.bundleFile}`);
37
+ const plugins = options.typescript ? [(0, esbuild_plugin_tsc_1.default)({ force: true })] : [];
38
+ await esbuild.build({
39
+ entryPoints: [options.entrypointFile],
40
+ outfile: options.bundleFile,
41
+ bundle: true,
42
+ minify: false,
43
+ sourcemap: true,
44
+ format: "iife",
45
+ globalName: "gatling",
46
+ plugins
47
+ });
48
+ };
49
+ exports.bundle = bundle;
@@ -0,0 +1 @@
1
+ export declare const downloadFile: (url: string, targetFile: string) => Promise<void>;
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.downloadFile = void 0;
7
+ const axios_1 = __importDefault(require("axios"));
8
+ const fs_1 = __importDefault(require("fs"));
9
+ const stream_1 = __importDefault(require("stream"));
10
+ const util_1 = __importDefault(require("util"));
11
+ const pipeline = util_1.default.promisify(stream_1.default.pipeline);
12
+ const downloadFile = async (url, targetFile) => {
13
+ const request = await axios_1.default.get(url, { responseType: "stream" });
14
+ await pipeline(request.data, fs_1.default.createWriteStream(targetFile));
15
+ };
16
+ exports.downloadFile = downloadFile;
@@ -0,0 +1,12 @@
1
+ /// <reference types="node" />
2
+ import { exec } from "child_process";
3
+ export declare const execAsync: typeof exec.__promisify__;
4
+ export interface DependenciesOptions {
5
+ gatlingHome: string;
6
+ }
7
+ export interface ResolvedDependencies {
8
+ graalvmHome: string;
9
+ coursierBinary: string;
10
+ jvmClasspath: string;
11
+ }
12
+ export declare const installAll: (options: DependenciesOptions) => Promise<ResolvedDependencies>;
@@ -0,0 +1,127 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.installAll = exports.execAsync = void 0;
7
+ const child_process_1 = require("child_process");
8
+ const decompress_1 = __importDefault(require("decompress"));
9
+ const fs_1 = require("fs");
10
+ const promises_1 = __importDefault(require("fs/promises"));
11
+ const os_1 = __importDefault(require("os"));
12
+ const util_1 = require("util");
13
+ const download_1 = require("./download");
14
+ const log_1 = require("../log");
15
+ const versions_1 = require("./versions");
16
+ exports.execAsync = (0, util_1.promisify)(child_process_1.exec);
17
+ const installAll = async (options) => {
18
+ const downloadDir = `${options.gatlingHome}/tmp/download`;
19
+ await promises_1.default.mkdir(downloadDir, { recursive: true });
20
+ const graalvmHomePath = await installGraalVm(options.gatlingHome, downloadDir);
21
+ const coursierPath = await installCoursier(options.gatlingHome, downloadDir);
22
+ const classpath = await resolveDependencies(coursierPath, graalvmHomePath);
23
+ return {
24
+ graalvmHome: graalvmHomePath,
25
+ coursierBinary: coursierPath,
26
+ jvmClasspath: classpath.trim()
27
+ };
28
+ };
29
+ exports.installAll = installAll;
30
+ const installGraalVm = async (gatlingHomeDir, downloadDir) => {
31
+ // TODO test Linux and Windows
32
+ const { os, arch, extension, homePath } = graalVmPlatformParams();
33
+ const graalvmRootPath = `${gatlingHomeDir}/graalvm/${versions_1.versions.graalvm.jdk}`;
34
+ const graalvmHomePath = `${graalvmRootPath}${homePath}`;
35
+ const graalvmJavaPath = `${graalvmHomePath}/bin/java`;
36
+ if (!(0, fs_1.existsSync)(graalvmJavaPath)) {
37
+ const url = `https://github.com/graalvm/graalvm-ce-builds/releases/download/jdk-${versions_1.versions.graalvm.jdk}/graalvm-community-jdk-${versions_1.versions.graalvm.jdk}_${os}-${arch}_bin.${extension}`;
38
+ const downloadPath = `${downloadDir}/graalvm.${extension}`;
39
+ if ((0, fs_1.existsSync)(graalvmRootPath)) {
40
+ await promises_1.default.rm(graalvmRootPath, { recursive: true });
41
+ }
42
+ if ((0, fs_1.existsSync)(downloadPath)) {
43
+ await promises_1.default.rm(downloadPath);
44
+ }
45
+ await promises_1.default.mkdir(graalvmRootPath, { recursive: true });
46
+ log_1.logger.info(`Downloading GraalVM Community Edition ${versions_1.versions.graalvm.jdk} to ${downloadPath}`);
47
+ await (0, download_1.downloadFile)(url, downloadPath);
48
+ log_1.logger.info(`Unpacking GraalVM to ${graalvmRootPath}`);
49
+ await (0, decompress_1.default)(downloadPath, graalvmRootPath, {
50
+ map: (file) => {
51
+ // Remove first level of file name, because it already contains a root directory
52
+ file.path = file.path.split("/").slice(1).join("/");
53
+ return file;
54
+ }
55
+ });
56
+ await promises_1.default.rm(downloadPath);
57
+ }
58
+ else {
59
+ log_1.logger.info(`GraalVM Community Edition ${versions_1.versions.graalvm.jdk} already installed at ${graalvmRootPath}`);
60
+ }
61
+ return graalvmHomePath;
62
+ };
63
+ const graalVmPlatformParams = () => {
64
+ const osType = os_1.default.type(); // 'Darwin', 'Linux', 'Windows_NT'
65
+ const osArch = os_1.default.arch(); // 'x64', 'arm64', etc.
66
+ if (osType === "Linux") {
67
+ const os = "linux";
68
+ const extension = "tar.gz";
69
+ const homePath = "";
70
+ if (osArch === "x64") {
71
+ return { os, arch: "x64", extension, homePath };
72
+ }
73
+ else if (osArch === "arm64") {
74
+ return { os, arch: "aarch64", extension, homePath };
75
+ }
76
+ }
77
+ else if (osType === "Darwin") {
78
+ const os = "macos";
79
+ const extension = "tar.gz";
80
+ const homePath = "/Contents/Home";
81
+ if (osArch === "x64") {
82
+ return { os, arch: "x64", extension, homePath };
83
+ }
84
+ else if (osArch === "arm64") {
85
+ return { os, arch: "aarch64", extension, homePath };
86
+ }
87
+ }
88
+ else if (osType === "Windows_NT" && osArch === "x64") {
89
+ return { os: "windows", arch: "x64", extension: "zip", homePath: "" };
90
+ }
91
+ throw Error(`Operating system type '${osType}' with architecture '${osArch}' is not currently supported.`);
92
+ };
93
+ const installCoursier = async (gatlingHomeDir, downloadDir) => {
94
+ const coursierRootPath = `${gatlingHomeDir}/coursier/${versions_1.versions.coursier}`;
95
+ const coursierPath = `${coursierRootPath}/cs`;
96
+ if (!(0, fs_1.existsSync)(coursierPath)) {
97
+ // TODO test Linux/windows
98
+ const url = `https://github.com/coursier/coursier/releases/download/v${versions_1.versions.coursier}/coursier`;
99
+ const downloadPath = `${downloadDir}/cs`;
100
+ if ((0, fs_1.existsSync)(coursierRootPath)) {
101
+ await promises_1.default.rm(coursierRootPath, { recursive: true });
102
+ }
103
+ if ((0, fs_1.existsSync)(downloadPath)) {
104
+ await promises_1.default.rm(downloadPath);
105
+ }
106
+ await promises_1.default.mkdir(coursierRootPath, { recursive: true });
107
+ log_1.logger.info(`Downloading Coursier ${versions_1.versions.coursier} to ${downloadPath}`);
108
+ await (0, download_1.downloadFile)(url, downloadPath);
109
+ await promises_1.default.chmod(downloadPath, 0o744);
110
+ log_1.logger.info(`Installing Coursier to ${coursierPath}`);
111
+ await promises_1.default.rename(downloadPath, coursierPath);
112
+ }
113
+ else {
114
+ log_1.logger.info(`Coursier ${versions_1.versions.coursier} already installed at ${coursierPath}`);
115
+ }
116
+ return coursierPath;
117
+ };
118
+ const resolveDependencies = async (coursierPath, javaHome) => {
119
+ const gatlingDep = `"io.gatling.highcharts:gatling-charts-highcharts:${versions_1.versions.gatling.core}"`;
120
+ const gatlingAdapterDep = `"io.gatling:gatling-jvm-to-js-adapter:${versions_1.versions.gatling.jsAdapter}"`;
121
+ const graalvmJsDep = `"org.graalvm.polyglot:js-community:${versions_1.versions.graalvm.js}"`;
122
+ const command = `"${coursierPath}" fetch --classpath ${gatlingDep} ${gatlingAdapterDep} ${graalvmJsDep}`;
123
+ // TODO could add a timeout
124
+ log_1.logger.info(`Resolving dependencies with Coursier`);
125
+ const { stdout } = await (0, exports.execAsync)(command, { env: { ...process.env, JAVA_HOME: javaHome } });
126
+ return stdout;
127
+ };
@@ -0,0 +1,11 @@
1
+ export declare const versions: {
2
+ graalvm: {
3
+ jdk: string;
4
+ js: string;
5
+ };
6
+ coursier: string;
7
+ gatling: {
8
+ core: string;
9
+ jsAdapter: string;
10
+ };
11
+ };
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.versions = void 0;
4
+ exports.versions = {
5
+ graalvm: {
6
+ jdk: "22.0.1",
7
+ js: "24.0.1"
8
+ },
9
+ coursier: "2.1.9",
10
+ gatling: {
11
+ core: "3.11.2",
12
+ jsAdapter: "0.0.8+8-e84571c3-SNAPSHOT"
13
+ }
14
+ };
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,94 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ const commander_1 = require("commander");
8
+ const os_1 = __importDefault(require("os"));
9
+ const bundle_1 = require("./bundle");
10
+ const dependencies_1 = require("./dependencies");
11
+ const log_1 = require("./log");
12
+ const run_1 = require("./run");
13
+ const program = new commander_1.Command()
14
+ .name("gatling-js-cli")
15
+ .version("0.0.1")
16
+ .description("The Gatling Javascript run & packaging tool");
17
+ const gatlingHomeOption = new commander_1.Option("--gatling-home <value>", 'The folder used to download and install Gatling components (default: "~/.gatling")');
18
+ const gatlingHomeDirWithDefaults = (options) => options.gatlingHome || `${os_1.default.homedir()}/.gatling`;
19
+ const entrypointFileOption = new commander_1.Option("--entrypoint-file <value>", 'The simulation entry point source file path (default: "src/index.js", or "src/index.ts" when using the "--typescript" option)');
20
+ const entrypointNameOption = new commander_1.Option("--entrypoint-name <value>", "The simulation entry point function name").default("default", '"default", compatible with using "export default"');
21
+ const entrypointFileWithDefaults = (options) => options.entrypointFile || (options.typescript ? "src/index.ts" : "src/index.js");
22
+ const bundleFileOption = new commander_1.Option("--bundle-file <value>", "The simulation target bundle file path").default("target/bundle.js");
23
+ const resourcesFolderOption = new commander_1.Option("--resources-folder <value>", "The resources folder path").default("resources");
24
+ const resultsFolderOption = new commander_1.Option("--results-folder <value>", "The results folder path").default("results");
25
+ const typescriptOption = new commander_1.Option("--typescript", "Use the typescript compiler to compile your code").default(false);
26
+ const graalvmHomeMandatoryOption = new commander_1.Option("--graalvm-home <value>", "Path to the GraalVM home").makeOptionMandatory(true);
27
+ const jvmClasspathMandatoryOption = new commander_1.Option("--jvm-classpath <value>", "The classpath containing all Gatling JVM components").makeOptionMandatory(true);
28
+ program
29
+ .command("install")
30
+ .description("Install all required components and dependencies for Gatling")
31
+ .addOption(gatlingHomeOption)
32
+ .action(async (options) => {
33
+ const gatlingHome = gatlingHomeDirWithDefaults(options);
34
+ const { graalvmHome, coursierBinary, jvmClasspath } = await (0, dependencies_1.installAll)({ gatlingHome });
35
+ log_1.logger.info(`graalvmHome=${graalvmHome}`);
36
+ log_1.logger.info(`coursierBinary=${coursierBinary}`);
37
+ log_1.logger.info(`jvmClasspath=${jvmClasspath}`);
38
+ });
39
+ program
40
+ .command("build")
41
+ .description("Build a Gatling simulation")
42
+ .addOption(entrypointFileOption)
43
+ .addOption(bundleFileOption)
44
+ .addOption(typescriptOption)
45
+ .action(async (options) => {
46
+ const entrypointFile = entrypointFileWithDefaults(options);
47
+ const bundleFile = options.bundleFile;
48
+ const typescript = options.typescript;
49
+ await (0, bundle_1.bundle)({ entrypointFile, bundleFile, typescript });
50
+ });
51
+ program
52
+ .command("run-only")
53
+ .description("Run a Gatling simulation")
54
+ .addOption(graalvmHomeMandatoryOption)
55
+ .addOption(jvmClasspathMandatoryOption)
56
+ .addOption(entrypointNameOption)
57
+ .addOption(bundleFileOption)
58
+ .addOption(resourcesFolderOption)
59
+ .addOption(resultsFolderOption)
60
+ .action(async (options) => {
61
+ const graalvmHome = options.graalvmHome;
62
+ const jvmClasspath = options.jvmClasspath;
63
+ const entrypointName = options.entrypointName;
64
+ const bundleFile = options.bundleFile;
65
+ const resourcesFolder = options.resourcesFolder;
66
+ const resultsFolder = options.resultsFolder;
67
+ await (0, run_1.run)({ graalvmHome, jvmClasspath, entrypointName, bundleFile, resourcesFolder, resultsFolder });
68
+ });
69
+ program
70
+ .command("run")
71
+ .description("Build and run a Gatling simulation, after installing all required components and dependencies for Gatling")
72
+ .addOption(entrypointFileOption)
73
+ .addOption(entrypointNameOption)
74
+ .addOption(typescriptOption)
75
+ .addOption(bundleFileOption)
76
+ .addOption(resourcesFolderOption)
77
+ .addOption(resultsFolderOption)
78
+ .addOption(gatlingHomeOption)
79
+ .action(async (options) => {
80
+ const gatlingHome = gatlingHomeDirWithDefaults(options);
81
+ const entrypointFile = entrypointFileWithDefaults(options);
82
+ const entrypointName = options.entrypointName;
83
+ const bundleFile = options.bundleFile;
84
+ const resourcesFolder = options.resourcesFolder;
85
+ const resultsFolder = options.resultsFolder;
86
+ const typescript = options.typescript;
87
+ const { graalvmHome, coursierBinary, jvmClasspath } = await (0, dependencies_1.installAll)({ gatlingHome });
88
+ log_1.logger.debug(`graalvmHome=${graalvmHome}`);
89
+ log_1.logger.debug(`coursierBinary=${coursierBinary}`);
90
+ log_1.logger.debug(`jvmClasspath=${jvmClasspath}`);
91
+ await (0, bundle_1.bundle)({ entrypointFile, bundleFile, typescript });
92
+ await (0, run_1.run)({ graalvmHome, jvmClasspath, entrypointName, bundleFile, resourcesFolder, resultsFolder });
93
+ });
94
+ program.parse(process.argv);
@@ -0,0 +1,6 @@
1
+ export interface Logger {
2
+ debug: (message: string) => void;
3
+ info: (message: string) => void;
4
+ error: (message: string) => void;
5
+ }
6
+ export declare const logger: Logger;
package/target/log.js ADDED
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.logger = void 0;
4
+ const debug =
5
+ // On Node.js, console.debug is just an alias to console.log, so we handle debug level ourselves
6
+ process.env["DEBUG"] === "true" ? console.debug : () => { };
7
+ const info = console.info;
8
+ const error = console.error;
9
+ exports.logger = {
10
+ debug,
11
+ info,
12
+ error
13
+ };
@@ -0,0 +1,9 @@
1
+ export interface RunOptions {
2
+ graalvmHome: string;
3
+ jvmClasspath: string;
4
+ entrypointName: string;
5
+ bundleFile: string;
6
+ resourcesFolder: string;
7
+ resultsFolder: string;
8
+ }
9
+ export declare const run: (options: RunOptions) => Promise<void>;
package/target/run.js ADDED
@@ -0,0 +1,68 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ Object.defineProperty(exports, "__esModule", { value: true });
26
+ exports.run = void 0;
27
+ const child_process_1 = require("child_process");
28
+ const path = __importStar(require("path"));
29
+ const log_1 = require("./log");
30
+ const run = async (options) => {
31
+ log_1.logger.info(`Running a Gatling simulation with options:
32
+ - entrypointName: ${options.entrypointName}
33
+ - bundleFile: ${options.bundleFile}`);
34
+ const bundleFolder = path.parse(options.bundleFile).dir;
35
+ const bundleFileName = path.parse(options.bundleFile).base;
36
+ const command = `${options.graalvmHome}/bin/java`;
37
+ const args = [
38
+ "-server",
39
+ "-XX:+HeapDumpOnOutOfMemoryError",
40
+ "-XX:MaxInlineLevel=20",
41
+ "-XX:MaxTrivialSize=12",
42
+ "-Xmx1G",
43
+ "-classpath",
44
+ `${bundleFolder}:${options.resourcesFolder}:${options.jvmClasspath}`,
45
+ `-Dgatling.js.bundle.resourcePath=${bundleFileName}`,
46
+ `-Dgatling.js.entrypointName=${options.entrypointName}`,
47
+ "io.gatling.app.Gatling",
48
+ "--results-folder",
49
+ options.resultsFolder,
50
+ "--simulation",
51
+ "io.gatling.js.JsSimulation"
52
+ ];
53
+ const process = (0, child_process_1.spawn)(command, args);
54
+ return new Promise((resolve, reject) => {
55
+ process.stdout.on("data", (data) => log_1.logger.info(data.toString()));
56
+ process.stderr.on("data", (data) => log_1.logger.error(data.toString()));
57
+ process.on("error", (error) => log_1.logger.error("Failed to run Gatling process: " + error.toString()));
58
+ process.on("close", (code) => {
59
+ if (code === 0) {
60
+ resolve();
61
+ }
62
+ else {
63
+ reject(Error("Gatling process finished with code " + code));
64
+ }
65
+ });
66
+ });
67
+ };
68
+ exports.run = run;