@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.
Files changed (63) hide show
  1. package/package.json +8 -6
  2. package/polyfills/crypto.js +87 -0
  3. package/polyfills/global.js +13 -0
  4. package/src/bundle/index.ts +43 -0
  5. package/src/bundle/polyfill.ts +91 -0
  6. package/src/commands/build.ts +34 -0
  7. package/src/commands/enterpriseDeploy.ts +97 -0
  8. package/src/commands/enterprisePackage.ts +45 -0
  9. package/src/commands/enterpriseStart.ts +122 -0
  10. package/src/commands/index.ts +25 -0
  11. package/src/commands/install.ts +19 -0
  12. package/src/commands/options.ts +308 -0
  13. package/src/commands/recorder.ts +41 -0
  14. package/src/commands/run.ts +82 -0
  15. package/src/commands/runOnly.ts +56 -0
  16. package/src/dependencies/coursier.ts +84 -0
  17. package/src/dependencies/download.ts +11 -0
  18. package/src/dependencies/graalVm.ts +74 -0
  19. package/src/dependencies/index.ts +45 -0
  20. package/src/dependencies/os.ts +24 -0
  21. package/src/dependencies/versions.ts +15 -0
  22. package/src/enterprise.ts +227 -0
  23. package/src/index.ts +5 -0
  24. package/src/java.ts +48 -0
  25. package/src/log.ts +19 -0
  26. package/src/readline.ts +39 -0
  27. package/src/run.ts +67 -0
  28. package/src/simulations.ts +29 -0
  29. package/target/{bundle.d.ts → bundle/index.d.ts} +2 -1
  30. package/target/{bundle.js → bundle/index.js} +7 -1
  31. package/target/bundle/polyfill.d.ts +2 -0
  32. package/target/bundle/polyfill.js +83 -0
  33. package/target/commands/build.d.ts +3 -0
  34. package/target/commands/build.js +22 -0
  35. package/target/commands/enterpriseDeploy.d.ts +3 -0
  36. package/target/commands/enterpriseDeploy.js +64 -0
  37. package/target/commands/enterprisePackage.d.ts +3 -0
  38. package/target/commands/enterprisePackage.js +28 -0
  39. package/target/commands/enterpriseStart.d.ts +3 -0
  40. package/target/commands/enterpriseStart.js +80 -0
  41. package/target/commands/index.d.ts +2 -0
  42. package/target/commands/index.js +28 -0
  43. package/target/commands/install.d.ts +3 -0
  44. package/target/commands/install.js +18 -0
  45. package/target/commands/options.d.ts +50 -0
  46. package/target/commands/options.js +214 -0
  47. package/target/commands/recorder.d.ts +3 -0
  48. package/target/commands/recorder.js +28 -0
  49. package/target/commands/run.d.ts +3 -0
  50. package/target/commands/run.js +53 -0
  51. package/target/commands/runOnly.d.ts +3 -0
  52. package/target/commands/runOnly.js +37 -0
  53. package/target/dependencies/coursier.d.ts +1 -1
  54. package/target/dependencies/coursier.js +11 -6
  55. package/target/dependencies/index.d.ts +2 -0
  56. package/target/dependencies/index.js +4 -2
  57. package/target/dependencies/versions.d.ts +3 -0
  58. package/target/dependencies/versions.js +9 -6
  59. package/target/enterprise.d.ts +2 -1
  60. package/target/enterprise.js +7 -6
  61. package/target/index.js +2 -337
  62. package/target/run.js +4 -2
  63. 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,15 @@
1
+ export const versions = {
2
+ graalvm: {
3
+ jdk: "23.0.0",
4
+ js: "24.1.1"
5
+ },
6
+ java: {
7
+ compilerRelease: "21"
8
+ },
9
+ coursier: "2.1.12",
10
+ gatling: {
11
+ core: "3.13.1",
12
+ enterprisePluginCommons: "1.9.8",
13
+ jsAdapter: "3.13.1"
14
+ }
15
+ };
@@ -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
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { program } from "./commands";
4
+
5
+ program.parse(process.argv);
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
+ };
@@ -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
+ };
@@ -1,7 +1,8 @@
1
- import { SimulationFile } from "./simulations";
1
+ import { SimulationFile } from "../simulations";
2
2
  export interface BundleOptions {
3
3
  sourcesFolder: string;
4
4
  bundleFile: string;
5
+ postman?: string;
5
6
  typescript: boolean;
6
7
  simulations: SimulationFile[];
7
8
  }
@@ -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 log_1 = require("./log");
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,2 @@
1
+ import type { Plugin } from "esbuild";
2
+ export declare const polyfill: () => Plugin;
@@ -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,3 @@
1
+ import { Command } from "commander";
2
+ declare const _default: (program: Command) => void;
3
+ export default _default;
@@ -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
+ };