@elastic/synthetics 1.0.0-beta.33 → 1.0.0-beta.35

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 (44) hide show
  1. package/dist/cli.js +7 -4
  2. package/dist/cli.js.map +1 -1
  3. package/dist/common_types.d.ts +8 -4
  4. package/dist/common_types.d.ts.map +1 -1
  5. package/dist/config.d.ts +1 -0
  6. package/dist/config.d.ts.map +1 -1
  7. package/dist/config.js +2 -1
  8. package/dist/config.js.map +1 -1
  9. package/dist/generator/index.d.ts +1 -6
  10. package/dist/generator/index.d.ts.map +1 -1
  11. package/dist/generator/index.js +5 -14
  12. package/dist/generator/index.js.map +1 -1
  13. package/dist/generator/utils.d.ts.map +1 -1
  14. package/dist/generator/utils.js +3 -0
  15. package/dist/generator/utils.js.map +1 -1
  16. package/dist/push/bundler.d.ts +1 -0
  17. package/dist/push/bundler.d.ts.map +1 -1
  18. package/dist/push/bundler.js +16 -1
  19. package/dist/push/bundler.js.map +1 -1
  20. package/dist/push/index.d.ts +1 -2
  21. package/dist/push/index.d.ts.map +1 -1
  22. package/dist/push/index.js +103 -76
  23. package/dist/push/index.js.map +1 -1
  24. package/dist/push/monitor.js +1 -1
  25. package/dist/push/monitor.js.map +1 -1
  26. package/dist/push/plugin.d.ts.map +1 -1
  27. package/dist/push/plugin.js +30 -7
  28. package/dist/push/plugin.js.map +1 -1
  29. package/dist/push/request.d.ts.map +1 -1
  30. package/dist/push/request.js +5 -5
  31. package/dist/push/request.js.map +1 -1
  32. package/dist/tsconfig.tsbuildinfo +1 -1
  33. package/package.json +5 -3
  34. package/src/cli.ts +12 -7
  35. package/src/common_types.ts +9 -4
  36. package/src/config.ts +1 -1
  37. package/src/generator/index.ts +5 -20
  38. package/src/generator/utils.ts +3 -0
  39. package/src/push/bundler.ts +21 -2
  40. package/src/push/index.ts +111 -89
  41. package/src/push/monitor.ts +1 -1
  42. package/src/push/plugin.ts +38 -9
  43. package/src/push/request.ts +9 -7
  44. package/templates/synthetics.config.ts +8 -0
package/src/push/index.ts CHANGED
@@ -23,11 +23,9 @@
23
23
  *
24
24
  */
25
25
 
26
- import { join } from 'path';
27
- import { existsSync } from 'fs';
28
26
  import { readFile, writeFile } from 'fs/promises';
29
27
  import { prompt } from 'enquirer';
30
- import { bold, yellow } from 'kleur/colors';
28
+ import { bold } from 'kleur/colors';
31
29
  import {
32
30
  ok,
33
31
  formatAPIError,
@@ -36,40 +34,57 @@ import {
36
34
  formatStaleMonitors,
37
35
  } from './request';
38
36
  import { buildMonitorSchema, createMonitors, MonitorSchema } from './monitor';
39
- import { ProjectSettings } from '../generator';
40
37
  import { Monitor } from '../dsl/monitor';
41
38
  import {
42
39
  progress,
43
40
  apiProgress,
44
41
  write,
45
42
  error,
46
- SYNTHETICS_PATH,
47
43
  aborted,
48
44
  indent,
49
45
  safeNDJSONParse,
50
46
  done,
51
47
  } from '../helpers';
52
- import type { PushOptions } from '../common_types';
48
+ import type { PushOptions, ProjectSettings } from '../common_types';
49
+ import { findSyntheticsConfig, readConfig } from '../config';
53
50
 
54
51
  export async function push(monitors: Monitor[], options: PushOptions) {
55
- if (monitors.length === 0) {
56
- throw 'No Monitors found';
57
- }
58
- const duplicates = trackDuplicates(monitors);
59
- if (duplicates.size > 0) {
60
- throw error(formatDuplicateError(duplicates));
61
- }
52
+ let schemas: MonitorSchema[] = [];
53
+ if (monitors.length > 0) {
54
+ const duplicates = trackDuplicates(monitors);
55
+ if (duplicates.size > 0) {
56
+ throw error(formatDuplicateError(duplicates));
57
+ }
62
58
 
63
- progress(`preparing all monitors`);
64
- const schemas = await buildMonitorSchema(monitors);
59
+ progress(`preparing all monitors`);
60
+ schemas = await buildMonitorSchema(monitors);
65
61
 
66
- progress(`creating all monitors`);
67
- for (const schema of schemas) {
68
- await pushMonitors({ schemas: [schema], keepStale: true, options });
62
+ progress(`creating all monitors`);
63
+ for (const schema of schemas) {
64
+ await pushMonitors({ schemas: [schema], keepStale: true, options });
65
+ }
66
+ } else {
67
+ write('');
68
+ const { deleteAll } = await prompt<{ deleteAll: boolean }>({
69
+ type: 'confirm',
70
+ skip() {
71
+ if (options.yes) {
72
+ this.initial = process.env.TEST_OVERRIDE ?? true;
73
+ return true;
74
+ }
75
+ return false;
76
+ },
77
+ name: 'deleteAll',
78
+ message: `Pushing without any monitors will delete all monitors associated with the project.\n Do you want to continue?`,
79
+ initial: false,
80
+ });
81
+ if (!deleteAll) {
82
+ throw aborted('Push command Aborted');
83
+ }
69
84
  }
70
-
71
85
  progress(`deleting all stale monitors`);
72
86
  await pushMonitors({ schemas, keepStale: false, options });
87
+
73
88
  done('Pushed');
74
89
  }
75
90
 
@@ -82,43 +97,39 @@ export async function pushMonitors({
82
97
  keepStale: boolean;
83
98
  options: PushOptions;
84
99
  }) {
85
- try {
86
- const { body, statusCode } = await createMonitors(
87
- schemas,
88
- options,
89
- keepStale
90
- );
91
- if (statusCode === 404) {
92
- throw formatNotFoundError(await body.text());
93
- }
94
- if (!ok(statusCode)) {
95
- const { error, message } = await body.json();
96
- throw formatAPIError(statusCode, error, message);
97
- }
98
- body.setEncoding('utf-8');
99
- for await (const data of body) {
100
- // Its kind of hacky for now where Kibana streams the response by
101
- // writing the data as NDJSON events (data can be interleaved), we
102
- // distinguish the final data by checking if the event was a progress vs complete event
103
- const chunks = safeNDJSONParse(data);
104
- for (const chunk of chunks) {
105
- if (typeof chunk === 'string') {
106
- // TODO: add progress back for all states once we get the fix
107
- // on kibana side
108
- keepStale && apiProgress(chunk);
109
- continue;
110
- }
111
- const { failedMonitors, failedStaleMonitors } = chunk;
112
- if (failedMonitors && failedMonitors.length > 0) {
113
- throw formatFailedMonitors(failedMonitors);
114
- }
115
- if (failedStaleMonitors.length > 0) {
116
- write(yellow(formatStaleMonitors(failedStaleMonitors)));
117
- }
100
+ const { body, statusCode } = await createMonitors(
101
+ schemas,
102
+ options,
103
+ keepStale
104
+ );
105
+ if (statusCode === 404) {
106
+ throw formatNotFoundError(await body.text());
107
+ }
108
+ if (!ok(statusCode)) {
109
+ const { error, message } = await body.json();
110
+ throw formatAPIError(statusCode, error, message);
111
+ }
112
+ body.setEncoding('utf-8');
113
+ for await (const data of body) {
114
+ // Its kind of hacky for now where Kibana streams the response by
115
+ // writing the data as NDJSON events (data can be interleaved), we
116
+ // distinguish the final data by checking if the event was a progress vs complete event
117
+ const chunks = safeNDJSONParse(data);
118
+ for (const chunk of chunks) {
119
+ if (typeof chunk === 'string') {
120
+ // TODO: add progress back for all states once we get the fix
121
+ // on kibana side
122
+ keepStale && apiProgress(chunk);
123
+ continue;
124
+ }
125
+ const { failedMonitors, failedStaleMonitors } = chunk;
126
+ if (failedMonitors && failedMonitors.length > 0) {
127
+ throw formatFailedMonitors(failedMonitors);
128
+ }
129
+ if (failedStaleMonitors.length > 0) {
130
+ throw formatStaleMonitors(failedStaleMonitors);
118
131
  }
119
132
  }
120
- } catch (e) {
121
- error(e);
122
133
  }
123
134
  }
124
135
 
@@ -148,34 +159,39 @@ export function formatDuplicateError(monitors: Set<Monitor>) {
148
159
  return outer;
149
160
  }
150
161
 
151
- const PROJECT_SETTINGS_PATH = join(SYNTHETICS_PATH, 'project.json');
152
162
  const INSTALLATION_HELP = `Run 'npx @elastic/synthetics init' to create project with default settings.`;
153
163
 
154
164
  export async function loadSettings() {
155
- if (!existsSync(PROJECT_SETTINGS_PATH)) {
156
- throw error(`Aborted. Synthetics project not set up.
165
+ try {
166
+ const config = await readConfig(process.env['NODE_ENV'] || 'development');
167
+ // Missing config file, fake throw to capture as missing file
168
+ if (Object.keys(config).length === 0) {
169
+ throw '';
170
+ }
171
+ return config.project || ({} as ProjectSettings);
172
+ } catch (e) {
173
+ throw error(`Aborted (missing synthetics config file), Project not set up corrrectly.
157
174
 
158
175
  ${INSTALLATION_HELP}`);
159
176
  }
160
-
161
- const contents = await readFile(PROJECT_SETTINGS_PATH, 'utf-8');
162
- return JSON.parse(contents) as ProjectSettings;
163
177
  }
164
178
 
165
179
  export function validateSettings(opts: PushOptions) {
166
180
  const INVALID = 'Aborted. Invalid synthetics project settings.';
167
181
 
168
182
  let reason = '';
169
- if (!opts.project) {
170
- reason = 'Set project id via CLI as `--project <id>`';
183
+ if (!opts.id) {
184
+ reason = `Set project id via
185
+ - CLI '--id <id>'
186
+ - Config file 'project.id' field`;
171
187
  } else if (!opts.locations && !opts.privateLocations) {
172
188
  reason = `Set default location for all monitors via
173
- - CLI as '--locations <values...> or --privateLocations <values...>'
174
- - Synthetics config file 'monitors.locations' | 'monitors.privateLocations' field`;
189
+ - CLI '--locations <values...> or --privateLocations <values...>'
190
+ - Config file 'monitors.locations' | 'monitors.privateLocations' field`;
175
191
  } else if (!opts.schedule) {
176
192
  reason = `Set default schedule in minutes for all monitors via
177
- - CLI as '--schedule <mins>'
178
- - Synthetics config file 'monitors.schedule' field`;
193
+ - CLI '--schedule <mins>'
194
+ - Config file 'monitors.schedule' field`;
179
195
  }
180
196
 
181
197
  if (!reason) return;
@@ -187,40 +203,46 @@ ${reason}
187
203
  ${INSTALLATION_HELP}`);
188
204
  }
189
205
 
190
- async function overrideSettings(project: string) {
191
- const settings = JSON.parse(await readFile(PROJECT_SETTINGS_PATH, 'utf-8'));
192
- settings.project = project;
193
-
194
- await writeFile(
195
- PROJECT_SETTINGS_PATH,
196
- JSON.stringify(settings, null, 2),
197
- 'utf-8'
206
+ async function overrideSettings(oldValue: string, newValue: string) {
207
+ const cwd = process.cwd();
208
+ const configPath = await findSyntheticsConfig(cwd, cwd);
209
+ if (!configPath) {
210
+ throw aborted(`Unable to find synthetics config file: ${configPath}`);
211
+ }
212
+ const config = await readFile(configPath, 'utf-8');
213
+ const updatedConfig = config.replace(
214
+ `id: '${oldValue}'`,
215
+ `id: '${newValue}'`
198
216
  );
217
+ await writeFile(configPath, updatedConfig, 'utf-8');
199
218
  }
200
219
 
201
220
  export async function catchIncorrectSettings(
202
221
  settings: ProjectSettings,
203
222
  options: PushOptions
204
223
  ) {
205
- let override = !settings.project;
206
- if (settings.project && settings.project !== options.project) {
207
- if (typeof process.env.TEST_OVERRIDE != null) {
208
- override = Boolean(process.env.TEST_OVERRIDE);
209
- } else {
210
- // Add an extra line to make it easier to read the prompt
211
- write('');
212
- ({ override } = await prompt<{ override: boolean }>({
213
- type: 'confirm',
214
- name: 'override',
215
- message: `Monitors were pushed under the '${settings.project}' project. Are you sure you want to push them under the new '${options.project}' (note that this will duplicate the monitors, the old ones being orphaned)`,
216
- initial: false,
217
- }));
218
- }
224
+ let override = !settings.id;
225
+ if (settings.id && settings.id !== options.id) {
226
+ // Add an extra line to make it easier to read the prompt
227
+ write('');
228
+ ({ override } = await prompt<{ override: boolean }>({
229
+ type: 'confirm',
230
+ name: 'override',
231
+ skip() {
232
+ if (options.yes) {
233
+ this.initial = process.env.TEST_OVERRIDE ?? true;
234
+ return true;
235
+ }
236
+ return false;
237
+ },
238
+ message: `Monitors were pushed under the '${settings.id}' project. Are you sure you want to push them under the new '${options.id}' (note that this will duplicate the monitors, the old ones being orphaned)`,
239
+ initial: false,
240
+ }));
219
241
  if (!override) {
220
242
  throw aborted('Push command Aborted');
221
243
  }
222
244
  }
223
245
  if (override) {
224
- await overrideSettings(options.project);
246
+ await overrideSettings(settings.id, options.id);
225
247
  }
226
248
  }
@@ -81,7 +81,7 @@ export async function createMonitors(
81
81
  keepStale: boolean
82
82
  ) {
83
83
  const schema: APISchema = {
84
- project: options.project,
84
+ project: options.id,
85
85
  keep_stale: keepStale,
86
86
  monitors,
87
87
  };
@@ -23,14 +23,17 @@
23
23
  *
24
24
  */
25
25
 
26
- import path from 'path';
26
+ import { isAbsolute, dirname, extname, join } from 'path';
27
27
  import fs from 'fs/promises';
28
28
  import * as esbuild from 'esbuild';
29
29
 
30
+ const SOURCE_DIR = join(__dirname, '..', '..');
31
+ const SOURCE_NODE_MODULES = join(SOURCE_DIR, 'node_modules');
32
+
30
33
  export function commonOptions(): esbuild.BuildOptions {
31
34
  return {
32
35
  bundle: true,
33
- external: ['node_modules', '@elastic/synthetics'],
36
+ external: ['@elastic/synthetics'],
34
37
  minify: false,
35
38
  platform: 'node',
36
39
  logLevel: 'silent',
@@ -55,27 +58,49 @@ export function MultiAssetPlugin(callback: PluginCallback): esbuild.Plugin {
55
58
  // Note that we use `isAbsolute` to handle UNC/windows style paths like C:\path\to\thing
56
59
  // This is not necessary for relative directories since `.\file` is not supported as an import
57
60
  // nor is `~/path/to/file`.
58
- if (path.isAbsolute(str) || str.startsWith('./') || str.startsWith('../')) {
61
+ if (isAbsolute(str) || str.startsWith('./') || str.startsWith('../')) {
59
62
  return true;
60
63
  }
61
64
  return false;
62
65
  };
63
66
 
67
+ // If we're importing the @elastic/synthetics package
68
+ // directly from source instead of using the fully
69
+ // qualified name, we must skip it too. That's just
70
+ // so it doesn't get bundled on tests or when we locally
71
+ // refer to the package itself.
72
+ const isLocalSynthetics = (entryPath: string) => {
73
+ return entryPath.startsWith(SOURCE_DIR);
74
+ };
75
+
76
+ // When importing the local synthetics module directly
77
+ // it may import its own local dependencies, so we must
78
+ // make sure those will be resolved using Node's resolution
79
+ // algorithm, as they're still "node_modules" that we must bundle
80
+ const isLocalSyntheticsModule = (str: string) => {
81
+ return str.startsWith(SOURCE_NODE_MODULES);
82
+ };
83
+
64
84
  return {
65
85
  name: 'esbuild-multiasset-plugin',
66
86
  setup(build) {
67
87
  build.onResolve({ filter: /.*?/ }, async args => {
68
88
  // External and other packages need be marked external to
69
89
  // be removed from the bundle
70
- if (
71
- build.initialOptions.external?.includes(args.path) ||
72
- !isBare(args.path)
73
- ) {
90
+ if (build.initialOptions.external?.includes(args.path)) {
74
91
  return {
75
92
  external: true,
76
93
  };
77
94
  }
78
95
 
96
+ if (
97
+ !isBare(args.path) ||
98
+ args.importer.includes('/node_modules/') ||
99
+ isLocalSyntheticsModule(args.importer)
100
+ ) {
101
+ return;
102
+ }
103
+
79
104
  if (args.kind === 'entry-point') {
80
105
  return {
81
106
  path: args.path,
@@ -86,8 +111,12 @@ export function MultiAssetPlugin(callback: PluginCallback): esbuild.Plugin {
86
111
  // If the modules are resolved locally, then
87
112
  // use the imported path to get full path
88
113
  const entryPath =
89
- path.join(path.dirname(args.importer), args.path) +
90
- path.extname(args.importer);
114
+ join(dirname(args.importer), args.path) + extname(args.importer);
115
+
116
+ if (isLocalSynthetics(entryPath)) {
117
+ return { external: true };
118
+ }
119
+
91
120
  // Spin off another build to copy over the imported modules without bundling
92
121
  const result = await esbuild.build({
93
122
  ...commonOptions(),
@@ -23,7 +23,7 @@
23
23
  *
24
24
  */
25
25
 
26
- import { bold } from 'kleur/colors';
26
+ import { bold, red, yellow } from 'kleur/colors';
27
27
  import { Dispatcher, request } from 'undici';
28
28
  import { indent, symbols } from '../helpers';
29
29
 
@@ -47,7 +47,7 @@ export async function sendRequest(options: APIRequestOptions) {
47
47
  'user-agent': `Elastic/Synthetics ${version}`,
48
48
  'kbn-xsrf': 'true',
49
49
  },
50
- headersTimeout: 60 * 1000
50
+ headersTimeout: 60 * 1000,
51
51
  });
52
52
  }
53
53
 
@@ -62,8 +62,10 @@ export type APIMonitorError = {
62
62
  };
63
63
 
64
64
  export function formatNotFoundError(message: string) {
65
- return bold(
66
- `${symbols['failed']} Please check your kibana url and try again - 404:${message}`
65
+ return red(
66
+ bold(
67
+ `${symbols['failed']} Please check your kibana url and try again - 404:${message}`
68
+ )
67
69
  );
68
70
  }
69
71
 
@@ -78,7 +80,7 @@ export function formatAPIError(
78
80
  );
79
81
  inner += indent(message, ' ');
80
82
  outer += indent(inner);
81
- return outer;
83
+ return red(outer);
82
84
  }
83
85
 
84
86
  function formatMonitorError(errors: APIMonitorError[]) {
@@ -95,10 +97,10 @@ function formatMonitorError(errors: APIMonitorError[]) {
95
97
 
96
98
  export function formatFailedMonitors(errors: APIMonitorError[]) {
97
99
  const heading = bold(`${symbols['failed']} Error\n`);
98
- return heading + formatMonitorError(errors);
100
+ return red(heading + formatMonitorError(errors));
99
101
  }
100
102
 
101
103
  export function formatStaleMonitors(errors: APIMonitorError[]) {
102
104
  const heading = bold(`${symbols['warning']} Warnings\n`);
103
- return heading + formatMonitorError(errors);
105
+ return yellow(heading + formatMonitorError(errors));
104
106
  }
@@ -16,6 +16,14 @@ export default env => {
16
16
  locations: ['{{locations}}'],
17
17
  privateLocations: ['{{privateLocations}}'],
18
18
  },
19
+ /**
20
+ * Project monitors settings
21
+ */
22
+ project: {
23
+ id: '{{id}}',
24
+ url: '{{url}}',
25
+ space: '{{space}}',
26
+ },
19
27
  };
20
28
  if (env !== 'development') {
21
29
  /**