@elastic/synthetics 1.17.2 → 1.18.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/dist/cli.js.map +1 -1
- package/dist/common_types.d.ts.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +1 -1
- package/dist/config.js.map +1 -1
- package/dist/core/globals.d.ts +1 -0
- package/dist/core/globals.d.ts.map +1 -1
- package/dist/core/globals.js +12 -1
- package/dist/core/globals.js.map +1 -1
- package/dist/core/logger.d.ts.map +1 -1
- package/dist/core/logger.js +2 -7
- package/dist/core/logger.js.map +1 -1
- package/dist/core/mfa.d.ts.map +1 -1
- package/dist/core/mfa.js +1 -1
- package/dist/core/runner.d.ts.map +1 -1
- package/dist/core/runner.js +1 -0
- package/dist/core/runner.js.map +1 -1
- package/dist/dsl/monitor.d.ts +4 -0
- package/dist/dsl/monitor.d.ts.map +1 -1
- package/dist/dsl/monitor.js +6 -0
- package/dist/dsl/monitor.js.map +1 -1
- package/dist/formatter/javascript.d.ts.map +1 -1
- package/dist/formatter/javascript.js.map +1 -1
- package/dist/generator/utils.d.ts +1 -1
- package/dist/helpers.d.ts.map +1 -1
- package/dist/helpers.js.map +1 -1
- package/dist/plugins/tracing.js.map +1 -1
- package/dist/push/bundler.d.ts +2 -5
- package/dist/push/bundler.d.ts.map +1 -1
- package/dist/push/bundler.js +15 -30
- package/dist/push/bundler.js.map +1 -1
- package/dist/push/index.d.ts.map +1 -1
- package/dist/push/index.js +38 -15
- package/dist/push/index.js.map +1 -1
- package/dist/push/kibana_api.d.ts +2 -1
- package/dist/push/kibana_api.d.ts.map +1 -1
- package/dist/push/kibana_api.js +6 -4
- package/dist/push/kibana_api.js.map +1 -1
- package/dist/push/monitor.d.ts +5 -1
- package/dist/push/monitor.d.ts.map +1 -1
- package/dist/push/monitor.js +14 -2
- package/dist/push/monitor.js.map +1 -1
- package/dist/push/request.d.ts +2 -2
- package/dist/push/request.d.ts.map +1 -1
- package/dist/push/request.js +7 -6
- package/dist/push/request.js.map +1 -1
- package/dist/push/run-local.d.ts +27 -0
- package/dist/push/run-local.d.ts.map +1 -0
- package/dist/push/run-local.js +132 -0
- package/dist/push/run-local.js.map +1 -0
- package/dist/push/utils.d.ts +6 -1
- package/dist/push/utils.d.ts.map +1 -1
- package/dist/push/utils.js +61 -6
- package/dist/push/utils.js.map +1 -1
- package/dist/reporters/base.d.ts.map +1 -1
- package/dist/reporters/base.js.map +1 -1
- package/dist/reporters/build_kite_cli.d.ts.map +1 -1
- package/dist/reporters/build_kite_cli.js.map +1 -1
- package/dist/reporters/json.d.ts +1 -1
- package/dist/reporters/json.d.ts.map +1 -1
- package/dist/reporters/json.js +6 -2
- package/dist/reporters/json.js.map +1 -1
- package/dist/reporters/junit.d.ts.map +1 -1
- package/dist/reporters/junit.js.map +1 -1
- package/package.json +13 -10
- package/src/cli.ts +8 -3
- package/src/common_types.ts +1 -1
- package/src/config.ts +3 -1
- package/src/core/globals.ts +12 -0
- package/src/core/logger.ts +2 -8
- package/src/core/mfa.ts +11 -11
- package/src/core/runner.ts +1 -0
- package/src/dsl/monitor.ts +7 -0
- package/src/formatter/javascript.ts +3 -1
- package/src/helpers.ts +3 -3
- package/src/loader.ts +1 -1
- package/src/plugins/tracing.ts +1 -1
- package/src/push/bundler.ts +16 -36
- package/src/push/index.ts +59 -24
- package/src/push/kibana_api.ts +7 -3
- package/src/push/monitor.ts +18 -5
- package/src/push/request.ts +26 -7
- package/src/push/run-local.ts +155 -0
- package/src/push/utils.ts +87 -9
- package/src/reporters/base.ts +8 -4
- package/src/reporters/build_kite_cli.ts +7 -6
- package/src/reporters/json.ts +10 -14
- package/src/reporters/junit.ts +4 -10
package/src/push/bundler.ts
CHANGED
|
@@ -24,25 +24,19 @@
|
|
|
24
24
|
*/
|
|
25
25
|
|
|
26
26
|
import path from 'path';
|
|
27
|
-
import {
|
|
27
|
+
import { unlink, readFile } from 'fs/promises';
|
|
28
28
|
import { createWriteStream } from 'fs';
|
|
29
29
|
import * as esbuild from 'esbuild';
|
|
30
30
|
import archiver from 'archiver';
|
|
31
31
|
import { commonOptions } from '../core/transform';
|
|
32
32
|
import { SyntheticsBundlePlugin } from './plugin';
|
|
33
33
|
|
|
34
|
-
// 1500KB Max Gzipped limit for bundled code to be pushed as Kibana project monitors.
|
|
35
|
-
const SIZE_LIMIT_KB = 1500;
|
|
36
|
-
|
|
37
34
|
function relativeToCwd(entry: string) {
|
|
38
35
|
return path.relative(process.cwd(), entry);
|
|
39
36
|
}
|
|
40
37
|
|
|
41
38
|
export class Bundler {
|
|
42
|
-
|
|
43
|
-
constructor() {}
|
|
44
|
-
|
|
45
|
-
async prepare(absPath: string) {
|
|
39
|
+
async bundle(absPath: string) {
|
|
46
40
|
const options: esbuild.BuildOptions = {
|
|
47
41
|
...commonOptions(),
|
|
48
42
|
...{
|
|
@@ -59,56 +53,42 @@ export class Bundler {
|
|
|
59
53
|
if (result.errors.length > 0) {
|
|
60
54
|
throw result.errors;
|
|
61
55
|
}
|
|
62
|
-
|
|
56
|
+
return result.outputFiles[0].text;
|
|
63
57
|
}
|
|
64
58
|
|
|
65
|
-
async zip(
|
|
59
|
+
async zip(source: string, code: string, dest: string) {
|
|
66
60
|
return new Promise((fulfill, reject) => {
|
|
67
|
-
const output = createWriteStream(
|
|
61
|
+
const output = createWriteStream(dest);
|
|
68
62
|
const archive = archiver('zip', {
|
|
69
63
|
zlib: { level: 9 },
|
|
70
64
|
});
|
|
71
65
|
archive.on('error', reject);
|
|
72
66
|
output.on('close', fulfill);
|
|
73
67
|
archive.pipe(output);
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
});
|
|
82
|
-
}
|
|
68
|
+
const relativePath = relativeToCwd(source);
|
|
69
|
+
// Date is fixed to Unix epoch so the file metadata is
|
|
70
|
+
// not modified everytime when files are bundled
|
|
71
|
+
archive.append(code, {
|
|
72
|
+
name: relativePath,
|
|
73
|
+
date: new Date('1970-01-01'),
|
|
74
|
+
});
|
|
83
75
|
archive.finalize();
|
|
84
76
|
});
|
|
85
77
|
}
|
|
86
78
|
|
|
87
79
|
async build(entry: string, output: string) {
|
|
88
|
-
await this.
|
|
89
|
-
await this.zip(output);
|
|
90
|
-
const
|
|
91
|
-
await this.checkSize(output);
|
|
80
|
+
const code = await this.bundle(entry);
|
|
81
|
+
await this.zip(entry, code, output);
|
|
82
|
+
const content = await this.encode(output);
|
|
92
83
|
await this.cleanup(output);
|
|
93
|
-
return
|
|
84
|
+
return content;
|
|
94
85
|
}
|
|
95
86
|
|
|
96
87
|
async encode(outputPath: string) {
|
|
97
88
|
return await readFile(outputPath, 'base64');
|
|
98
89
|
}
|
|
99
90
|
|
|
100
|
-
async checkSize(outputPath: string) {
|
|
101
|
-
const { size } = await stat(outputPath);
|
|
102
|
-
const sizeKb = size / 1024;
|
|
103
|
-
if (sizeKb > SIZE_LIMIT_KB) {
|
|
104
|
-
throw new Error(
|
|
105
|
-
`Bundled monitor code exceeds the recommended ${SIZE_LIMIT_KB}KB limit. Please check your dependencies and try again.`
|
|
106
|
-
);
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
91
|
async cleanup(outputPath: string) {
|
|
111
|
-
this.moduleMap = new Map<string, string>();
|
|
112
92
|
await unlink(outputPath);
|
|
113
93
|
}
|
|
114
94
|
}
|
package/src/push/index.ts
CHANGED
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
*/
|
|
25
25
|
import { readFile, writeFile } from 'fs/promises';
|
|
26
26
|
import { prompt } from 'enquirer';
|
|
27
|
-
import { bold,
|
|
27
|
+
import { bold, underline } from 'kleur/colors';
|
|
28
28
|
import {
|
|
29
29
|
getLocalMonitors,
|
|
30
30
|
buildMonitorSchema,
|
|
@@ -50,15 +50,20 @@ import {
|
|
|
50
50
|
bulkGetMonitors,
|
|
51
51
|
bulkPutMonitors,
|
|
52
52
|
createMonitorsLegacy,
|
|
53
|
-
|
|
53
|
+
BATCH_SIZE,
|
|
54
|
+
MAX_PAYLOAD_SIZE_KB,
|
|
54
55
|
} from './kibana_api';
|
|
55
56
|
import {
|
|
56
|
-
|
|
57
|
+
getBatches,
|
|
58
|
+
getSizedBatches,
|
|
57
59
|
isBulkAPISupported,
|
|
58
60
|
isLightweightMonitorSupported,
|
|
59
61
|
logDiff,
|
|
62
|
+
logGroups,
|
|
63
|
+
printBytes,
|
|
60
64
|
} from './utils';
|
|
61
|
-
import {
|
|
65
|
+
import { runLocal } from './run-local';
|
|
66
|
+
import { inDebugMode } from '../core/globals';
|
|
62
67
|
|
|
63
68
|
export async function push(monitors: Monitor[], options: PushOptions) {
|
|
64
69
|
if (parseInt(process.env.CHUNK_SIZE) > 250) {
|
|
@@ -84,7 +89,7 @@ export async function push(monitors: Monitor[], options: PushOptions) {
|
|
|
84
89
|
const { monitors: remote } = await bulkGetMonitors(options);
|
|
85
90
|
|
|
86
91
|
progress(`preparing ${monitors.length} monitors`);
|
|
87
|
-
const schemas = await buildMonitorSchema(monitors, true);
|
|
92
|
+
const { schemas, sizes } = await buildMonitorSchema(monitors, true);
|
|
88
93
|
const local = getLocalMonitors(schemas);
|
|
89
94
|
|
|
90
95
|
const { newIDs, changedIDs, removedIDs, unchangedIDs } = diffMonitorHashIDs(
|
|
@@ -92,21 +97,47 @@ export async function push(monitors: Monitor[], options: PushOptions) {
|
|
|
92
97
|
remote
|
|
93
98
|
);
|
|
94
99
|
logDiff(newIDs, changedIDs, removedIDs, unchangedIDs);
|
|
100
|
+
if (inDebugMode()) {
|
|
101
|
+
logGroups(sizes, newIDs, changedIDs, removedIDs, unchangedIDs);
|
|
102
|
+
// show bundle size for the whole project
|
|
103
|
+
let totalSize = 0;
|
|
104
|
+
for (const value of sizes.values()) {
|
|
105
|
+
totalSize += value;
|
|
106
|
+
}
|
|
107
|
+
progress(
|
|
108
|
+
`total size of the ${sizes.size} monitors payload is ` +
|
|
109
|
+
printBytes(totalSize)
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (options.dryRun) {
|
|
114
|
+
progress('Running browser monitors in dry run mode');
|
|
115
|
+
await runLocal(schemas);
|
|
116
|
+
progress('Dry run completed');
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
95
119
|
|
|
96
120
|
const updatedMonitors = new Set<string>([...changedIDs, ...newIDs]);
|
|
97
121
|
if (updatedMonitors.size > 0) {
|
|
98
122
|
const updatedMonSchemas = schemas.filter(s => updatedMonitors.has(s.id));
|
|
99
|
-
const
|
|
100
|
-
|
|
123
|
+
const batches = getSizedBatches(
|
|
124
|
+
updatedMonSchemas,
|
|
125
|
+
sizes,
|
|
126
|
+
MAX_PAYLOAD_SIZE_KB,
|
|
127
|
+
BATCH_SIZE
|
|
128
|
+
);
|
|
129
|
+
if (batches.length > 1) {
|
|
130
|
+
progress(`Monitors will be pushed as ${batches.length} batches.`);
|
|
131
|
+
}
|
|
132
|
+
for (const batch of batches) {
|
|
101
133
|
await liveProgress(
|
|
102
|
-
bulkPutMonitors(options,
|
|
103
|
-
`creating or updating ${
|
|
134
|
+
bulkPutMonitors(options, batch),
|
|
135
|
+
`creating or updating ${batch.length} monitors`
|
|
104
136
|
);
|
|
105
137
|
}
|
|
106
138
|
}
|
|
107
139
|
|
|
108
140
|
if (removedIDs.size > 0) {
|
|
109
|
-
log(`deleting monitor ids: ${Array.from(removedIDs.keys()).join(', ')}`);
|
|
110
141
|
if (updatedMonitors.size === 0 && unchangedIDs.size === 0) {
|
|
111
142
|
await confirmDelete(
|
|
112
143
|
`Pushing without any monitors will delete all monitors associated with the project.\n Do you want to continue?`,
|
|
@@ -118,16 +149,15 @@ export async function push(monitors: Monitor[], options: PushOptions) {
|
|
|
118
149
|
options.yes
|
|
119
150
|
);
|
|
120
151
|
}
|
|
121
|
-
const
|
|
122
|
-
for (const
|
|
152
|
+
const batches = getBatches(Array.from(removedIDs), BATCH_SIZE);
|
|
153
|
+
for (const batch of batches) {
|
|
123
154
|
await liveProgress(
|
|
124
|
-
bulkDeleteMonitors(options,
|
|
125
|
-
`deleting ${
|
|
155
|
+
bulkDeleteMonitors(options, batch),
|
|
156
|
+
`deleting ${batch.length} monitors`
|
|
126
157
|
);
|
|
127
158
|
}
|
|
128
159
|
}
|
|
129
|
-
|
|
130
|
-
done(`Pushed: ${grey(getMonitorManagementURL(options.url))}`);
|
|
160
|
+
done(`Pushed: ${underline(getMonitorManagementURL(options.url))} `);
|
|
131
161
|
}
|
|
132
162
|
|
|
133
163
|
async function confirmDelete(message: string, skip: boolean) {
|
|
@@ -223,8 +253,9 @@ export function validateSettings(opts: PushOptions) {
|
|
|
223
253
|
- CLI '--schedule <mins>'
|
|
224
254
|
- Config file 'monitors.schedule' field`;
|
|
225
255
|
} else if (opts.schedule && !ALLOWED_SCHEDULES.includes(opts.schedule)) {
|
|
226
|
-
reason = `Set default schedule(${
|
|
227
|
-
|
|
256
|
+
reason = `Set default schedule(${
|
|
257
|
+
opts.schedule
|
|
258
|
+
}) to one of the allowed values - ${ALLOWED_SCHEDULES.join(',')}`;
|
|
228
259
|
} else if (
|
|
229
260
|
(opts.locations ?? []).length > 0 &&
|
|
230
261
|
(opts?.playwrightOptions?.clientCertificates ?? []).filter(cert => {
|
|
@@ -299,14 +330,18 @@ export async function pushLegacy(monitors: Monitor[], options: PushOptions) {
|
|
|
299
330
|
}
|
|
300
331
|
|
|
301
332
|
let schemas: MonitorSchema[] = [];
|
|
333
|
+
let sizes: Map<string, number> = new Map();
|
|
302
334
|
if (monitors.length > 0) {
|
|
303
335
|
progress(`preparing ${monitors.length} monitors`);
|
|
304
|
-
schemas = await buildMonitorSchema(monitors, false);
|
|
305
|
-
const
|
|
306
|
-
|
|
336
|
+
({ schemas, sizes } = await buildMonitorSchema(monitors, false));
|
|
337
|
+
const batches = getSizedBatches(schemas, sizes, MAX_PAYLOAD_SIZE_KB, 10);
|
|
338
|
+
if (batches.length > 1) {
|
|
339
|
+
progress(`Monitors will be pushed as ${batches.length} batches.`);
|
|
340
|
+
}
|
|
341
|
+
for (const batch of batches) {
|
|
307
342
|
await liveProgress(
|
|
308
|
-
createMonitorsLegacy({ schemas:
|
|
309
|
-
`creating or updating ${
|
|
343
|
+
createMonitorsLegacy({ schemas: batch, keepStale: true, options }),
|
|
344
|
+
`creating or updating ${batch.length} monitors`
|
|
310
345
|
);
|
|
311
346
|
}
|
|
312
347
|
} else {
|
|
@@ -320,7 +355,7 @@ export async function pushLegacy(monitors: Monitor[], options: PushOptions) {
|
|
|
320
355
|
`deleting all stale monitors`
|
|
321
356
|
);
|
|
322
357
|
|
|
323
|
-
done(`Pushed: ${
|
|
358
|
+
done(`Pushed: ${underline(getMonitorManagementURL(options.url))}`);
|
|
324
359
|
}
|
|
325
360
|
|
|
326
361
|
// prints warning if any of the monitors has throttling settings enabled during push
|
package/src/push/kibana_api.ts
CHANGED
|
@@ -36,8 +36,9 @@ import {
|
|
|
36
36
|
} from './request';
|
|
37
37
|
import { generateURL } from './utils';
|
|
38
38
|
|
|
39
|
-
// Default
|
|
40
|
-
export const
|
|
39
|
+
// Default batch size for bulk put / delete
|
|
40
|
+
export const BATCH_SIZE = parseInt(process.env.CHUNK_SIZE) || 250;
|
|
41
|
+
export const MAX_PAYLOAD_SIZE_KB = 50 * 1000; // 50 MB in KB
|
|
41
42
|
|
|
42
43
|
export type PutResponse = {
|
|
43
44
|
createdMonitors: string[];
|
|
@@ -49,12 +50,15 @@ export async function bulkPutMonitors(
|
|
|
49
50
|
options: PushOptions,
|
|
50
51
|
schemas: MonitorSchema[]
|
|
51
52
|
) {
|
|
53
|
+
const url = generateURL(options, 'bulk_update') + '/_bulk_update';
|
|
54
|
+
|
|
52
55
|
const resp = await sendReqAndHandleError<PutResponse>({
|
|
53
|
-
url
|
|
56
|
+
url,
|
|
54
57
|
method: 'PUT',
|
|
55
58
|
auth: options.auth,
|
|
56
59
|
body: JSON.stringify({ monitors: schemas }),
|
|
57
60
|
});
|
|
61
|
+
|
|
58
62
|
const { failedMonitors } = resp;
|
|
59
63
|
if (failedMonitors && failedMonitors.length > 0) {
|
|
60
64
|
throw formatFailedMonitors(failedMonitors);
|
package/src/push/monitor.ts
CHANGED
|
@@ -42,12 +42,15 @@ import { isParamOptionSupported, normalizeMonitorName } from './utils';
|
|
|
42
42
|
|
|
43
43
|
// Allowed extensions for lightweight monitor files
|
|
44
44
|
const ALLOWED_LW_EXTENSIONS = ['.yml', '.yaml'];
|
|
45
|
+
// 1500kB Max Gzipped limit for bundled monitor code to be pushed as Kibana project monitors.
|
|
46
|
+
const SIZE_LIMIT_KB = 1500;
|
|
45
47
|
|
|
46
48
|
export type MonitorSchema = Omit<MonitorConfig, 'locations'> & {
|
|
47
49
|
locations: string[];
|
|
48
50
|
content?: string;
|
|
49
51
|
filter?: Monitor['filter'];
|
|
50
52
|
hash?: string;
|
|
53
|
+
size?: number;
|
|
51
54
|
};
|
|
52
55
|
|
|
53
56
|
// Abbreviated monitor info, as often returned by the API,
|
|
@@ -131,6 +134,7 @@ export async function buildMonitorSchema(monitors: Monitor[], isV2: boolean) {
|
|
|
131
134
|
await mkdir(bundlePath, { recursive: true });
|
|
132
135
|
const bundler = new Bundler();
|
|
133
136
|
const schemas: MonitorSchema[] = [];
|
|
137
|
+
const sizes: Map<string, number> = new Map();
|
|
134
138
|
|
|
135
139
|
for (const monitor of monitors) {
|
|
136
140
|
const { source, config, filter, type } = monitor;
|
|
@@ -148,6 +152,17 @@ export async function buildMonitorSchema(monitors: Monitor[], isV2: boolean) {
|
|
|
148
152
|
monitor.setContent(content);
|
|
149
153
|
Object.assign(schema, { content, filter });
|
|
150
154
|
}
|
|
155
|
+
const size = monitor.size();
|
|
156
|
+
const sizeKB = Math.round(size / 1000);
|
|
157
|
+
if (sizeKB > SIZE_LIMIT_KB) {
|
|
158
|
+
let outer = bold(
|
|
159
|
+
`Aborted: Bundled code ${sizeKB}kB exceeds the recommended ${SIZE_LIMIT_KB}kB limit. Please check the dependencies imported.\n`
|
|
160
|
+
);
|
|
161
|
+
const inner = `* ${config.id} - ${source.file}:${source.line}:${source.column}\n`;
|
|
162
|
+
outer += indent(inner);
|
|
163
|
+
throw red(outer);
|
|
164
|
+
}
|
|
165
|
+
sizes.set(config.id, size);
|
|
151
166
|
/**
|
|
152
167
|
* Generate hash only after the bundled content is created
|
|
153
168
|
* to capture code changes in imported files
|
|
@@ -159,7 +174,7 @@ export async function buildMonitorSchema(monitors: Monitor[], isV2: boolean) {
|
|
|
159
174
|
}
|
|
160
175
|
|
|
161
176
|
await rm(bundlePath, { recursive: true });
|
|
162
|
-
return schemas;
|
|
177
|
+
return { schemas, sizes };
|
|
163
178
|
}
|
|
164
179
|
|
|
165
180
|
export async function createLightweightMonitors(
|
|
@@ -225,9 +240,7 @@ export async function createLightweightMonitors(
|
|
|
225
240
|
const monitor = mergedConfig[i];
|
|
226
241
|
// Skip browser monitors from the YML files
|
|
227
242
|
if (monitor['type'] === 'browser') {
|
|
228
|
-
warn(
|
|
229
|
-
`Browser monitors from ${file} are skipped.`
|
|
230
|
-
);
|
|
243
|
+
warn(`Browser monitors from ${file} are skipped.`);
|
|
231
244
|
continue;
|
|
232
245
|
}
|
|
233
246
|
const { line, col } = lineCounter.linePos(offsets[i]);
|
|
@@ -288,7 +301,7 @@ export function buildMonitorFromYaml(
|
|
|
288
301
|
});
|
|
289
302
|
|
|
290
303
|
/**
|
|
291
|
-
* Params support is only available for
|
|
304
|
+
* Params support is only available for lightweight monitors
|
|
292
305
|
* post 8.7.2 stack
|
|
293
306
|
*/
|
|
294
307
|
if (isParamOptionSupported(options.kibanaVersion)) {
|
package/src/push/request.ts
CHANGED
|
@@ -58,8 +58,14 @@ export async function sendReqAndHandleError<T>(
|
|
|
58
58
|
options: APIRequestOptions
|
|
59
59
|
): Promise<T> {
|
|
60
60
|
const { statusCode, body } = await sendRequest(options);
|
|
61
|
+
|
|
61
62
|
return (
|
|
62
|
-
await handleError(
|
|
63
|
+
await handleError(
|
|
64
|
+
statusCode,
|
|
65
|
+
options.url,
|
|
66
|
+
body,
|
|
67
|
+
statusCode === 413 ? `${options.body?.length} bytes sent` : ''
|
|
68
|
+
)
|
|
63
69
|
).json() as Promise<T>;
|
|
64
70
|
}
|
|
65
71
|
|
|
@@ -74,7 +80,8 @@ type APIError = {
|
|
|
74
80
|
export async function handleError(
|
|
75
81
|
statusCode: number,
|
|
76
82
|
url: string,
|
|
77
|
-
body: Dispatcher.ResponseData['body']
|
|
83
|
+
body: Dispatcher.ResponseData['body'],
|
|
84
|
+
extraMessage?: string
|
|
78
85
|
): Promise<Dispatcher.ResponseData['body']> {
|
|
79
86
|
if (statusCode === 404) {
|
|
80
87
|
throw formatNotFoundError(url, await body.text());
|
|
@@ -84,9 +91,19 @@ export async function handleError(
|
|
|
84
91
|
const resp = await body.text();
|
|
85
92
|
parsed = JSON.parse(resp) as APIError;
|
|
86
93
|
} catch (e) {
|
|
87
|
-
throw formatAPIError(
|
|
94
|
+
throw formatAPIError(
|
|
95
|
+
statusCode,
|
|
96
|
+
'unexpected error',
|
|
97
|
+
e.message,
|
|
98
|
+
extraMessage
|
|
99
|
+
);
|
|
88
100
|
}
|
|
89
|
-
throw formatAPIError(
|
|
101
|
+
throw formatAPIError(
|
|
102
|
+
statusCode,
|
|
103
|
+
parsed.error,
|
|
104
|
+
parsed.message,
|
|
105
|
+
extraMessage
|
|
106
|
+
);
|
|
90
107
|
}
|
|
91
108
|
|
|
92
109
|
return body;
|
|
@@ -111,16 +128,18 @@ export function formatNotFoundError(url: string, message: string) {
|
|
|
111
128
|
}
|
|
112
129
|
|
|
113
130
|
export function formatAPIError(
|
|
114
|
-
|
|
131
|
+
statusCode: number,
|
|
115
132
|
error: string,
|
|
116
|
-
message: string
|
|
133
|
+
message: string,
|
|
134
|
+
extraMessage = ''
|
|
117
135
|
) {
|
|
118
136
|
let outer = bold(`${symbols['failed']} Error\n`);
|
|
119
137
|
let inner = bold(
|
|
120
|
-
`${symbols['failed']} monitor creation failed - ${
|
|
138
|
+
`${symbols['failed']} monitor creation failed - ${statusCode}:${error}\n`
|
|
121
139
|
);
|
|
122
140
|
inner += indent(message, ' ');
|
|
123
141
|
outer += indent(inner);
|
|
142
|
+
outer += extraMessage ? indent(extraMessage, ' ') : '';
|
|
124
143
|
return red(outer);
|
|
125
144
|
}
|
|
126
145
|
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MIT License
|
|
3
|
+
*
|
|
4
|
+
* Copyright (c) 2020-present, Elastic NV
|
|
5
|
+
*
|
|
6
|
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
* of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
* in the Software without restriction, including without limitation the rights
|
|
9
|
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
* copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
* furnished to do so, subject to the following conditions:
|
|
12
|
+
*
|
|
13
|
+
* The above copyright notice and this permission notice shall be included in
|
|
14
|
+
* all copies or substantial portions of the Software.
|
|
15
|
+
*
|
|
16
|
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
22
|
+
* THE SOFTWARE.
|
|
23
|
+
*
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import { execFileSync, spawn } from 'child_process';
|
|
27
|
+
import { rm, writeFile } from 'fs/promises';
|
|
28
|
+
import { createReadStream } from 'fs';
|
|
29
|
+
import { tmpdir } from 'os';
|
|
30
|
+
import { Extract } from 'unzip-stream';
|
|
31
|
+
import { red } from 'kleur/colors';
|
|
32
|
+
import { join } from 'path';
|
|
33
|
+
import { pathToFileURL } from 'url';
|
|
34
|
+
import { MonitorSchema } from './monitor';
|
|
35
|
+
|
|
36
|
+
async function unzipFile(zipPath, destination) {
|
|
37
|
+
return new Promise<void>((resolve, reject) => {
|
|
38
|
+
createReadStream(zipPath)
|
|
39
|
+
.pipe(Extract({ path: destination }))
|
|
40
|
+
.on('close', resolve)
|
|
41
|
+
.on('error', err =>
|
|
42
|
+
reject(new Error(`failed to extract zip ${zipPath} : ${err.message}`))
|
|
43
|
+
);
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function runNpmInstall(directory) {
|
|
48
|
+
return new Promise<void>((resolve, reject) => {
|
|
49
|
+
const flags = [
|
|
50
|
+
'--no-audit', // Prevent audit checks
|
|
51
|
+
'--no-update-notifier', // Prevent update checks
|
|
52
|
+
'--no-fund', // No need for package funding messages here
|
|
53
|
+
'--package-lock=false', // no need to write package lock here
|
|
54
|
+
'--progress=false', // no need to display progress
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
const npmInstall = spawn('npm', ['install', ...flags], {
|
|
58
|
+
cwd: directory,
|
|
59
|
+
stdio: 'ignore',
|
|
60
|
+
});
|
|
61
|
+
npmInstall.on('close', code => {
|
|
62
|
+
if (code === 0) {
|
|
63
|
+
resolve();
|
|
64
|
+
} else {
|
|
65
|
+
reject(new Error(`npm install failed with exit code ${code}`));
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
npmInstall.on('error', err =>
|
|
69
|
+
reject(new Error(`failed to setup: ${err.message}`))
|
|
70
|
+
);
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async function runTest(directory, schema: MonitorSchema) {
|
|
75
|
+
return new Promise<void>((resolve, reject) => {
|
|
76
|
+
const runTest = spawn(
|
|
77
|
+
'npx',
|
|
78
|
+
[
|
|
79
|
+
'@elastic/synthetics',
|
|
80
|
+
'.',
|
|
81
|
+
'--playwright-options',
|
|
82
|
+
JSON.stringify(schema.playwrightOptions),
|
|
83
|
+
'--params',
|
|
84
|
+
JSON.stringify(schema.params),
|
|
85
|
+
],
|
|
86
|
+
{
|
|
87
|
+
cwd: directory,
|
|
88
|
+
stdio: 'inherit',
|
|
89
|
+
}
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
runTest.on('close', resolve);
|
|
93
|
+
runTest.on('error', err => {
|
|
94
|
+
reject(
|
|
95
|
+
new Error(`Failed to execute @elastic/synthetics : ${err.message}`)
|
|
96
|
+
);
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async function writePkgJSON(dir: string, synthPath: string) {
|
|
102
|
+
const packageJsonContent = {
|
|
103
|
+
name: 'project-journey',
|
|
104
|
+
private: 'true',
|
|
105
|
+
dependencies: {
|
|
106
|
+
'@elastic/synthetics': pathToFileURL(synthPath),
|
|
107
|
+
},
|
|
108
|
+
};
|
|
109
|
+
await writeFile(
|
|
110
|
+
join(dir, 'package.json'),
|
|
111
|
+
JSON.stringify(packageJsonContent, null, 2),
|
|
112
|
+
'utf-8'
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async function extract(
|
|
117
|
+
schema: MonitorSchema,
|
|
118
|
+
zipPath: string,
|
|
119
|
+
unzipPath: string
|
|
120
|
+
) {
|
|
121
|
+
if (schema.type !== 'browser') {
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
const content = schema.content;
|
|
125
|
+
await writeFile(zipPath, content, 'base64');
|
|
126
|
+
await unzipFile(zipPath, unzipPath);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export async function runLocal(schemas: MonitorSchema[]) {
|
|
130
|
+
// lookup installed bin path of a node module
|
|
131
|
+
const resolvedPath = execFileSync('which', ['elastic-synthetics'], {
|
|
132
|
+
encoding: 'utf8',
|
|
133
|
+
}).trim();
|
|
134
|
+
const synthPath = resolvedPath.replace(
|
|
135
|
+
join('bin', 'elastic-synthetics'),
|
|
136
|
+
join('lib', 'node_modules', '@elastic/synthetics')
|
|
137
|
+
);
|
|
138
|
+
const rand = Date.now();
|
|
139
|
+
const zipPath = join(tmpdir(), `synthetics-zip-${rand}.zip`);
|
|
140
|
+
const unzipPath = join(tmpdir(), `synthetics-unzip-${rand}`);
|
|
141
|
+
try {
|
|
142
|
+
for (const schema of schemas) {
|
|
143
|
+
await extract(schema, zipPath, unzipPath);
|
|
144
|
+
}
|
|
145
|
+
await writePkgJSON(unzipPath, synthPath);
|
|
146
|
+
await runNpmInstall(unzipPath);
|
|
147
|
+
// TODO: figure out a way to collect all params and Playwright options
|
|
148
|
+
await runTest(unzipPath, schemas[0]);
|
|
149
|
+
} catch (e) {
|
|
150
|
+
throw red(`Aborted: ${e.message}`);
|
|
151
|
+
} finally {
|
|
152
|
+
await rm(zipPath, { recursive: true, force: true });
|
|
153
|
+
await rm(unzipPath, { recursive: true, force: true });
|
|
154
|
+
}
|
|
155
|
+
}
|