@elastic/synthetics 1.7.2 → 1.9.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/src/cli.ts CHANGED
@@ -56,33 +56,38 @@ import { installTransform } from './core/transform';
56
56
  /* eslint-disable-next-line @typescript-eslint/no-var-requires */
57
57
  const { name, version } = require('../package.json');
58
58
 
59
- const { params, pattern, playwrightOpts, auth, authMandatory } =
60
- getCommonCommandOpts();
59
+ const {
60
+ params,
61
+ pattern,
62
+ playwrightOpts,
63
+ auth,
64
+ authMandatory,
65
+ configOpt,
66
+ tags,
67
+ match,
68
+ } = getCommonCommandOpts();
61
69
 
62
70
  program
63
71
  .name(`npx ${name}`)
64
72
  .usage('[options] [dir] [files] file')
65
- .option(
66
- '-c, --config <path>',
67
- 'configuration path (default: synthetics.config.js)'
68
- )
73
+ .addOption(configOpt)
69
74
  .addOption(pattern)
75
+ .addOption(tags)
76
+ .addOption(match)
70
77
  .addOption(params)
71
- .option('--tags <name...>', 'run tests with a tag that matches the glob')
72
- .option(
73
- '--match <name>',
74
- 'run tests with a name or tags that matches the glob'
75
- )
76
78
  .addOption(
77
79
  new Option('--reporter <value>', `output reporter format`).choices(
78
80
  Object.keys(reporters)
79
81
  )
80
82
  )
81
- .option('--inline', 'Run inline journeys from heartbeat')
83
+ .option('--inline', 'read journeys from stdin instead of reading from files')
82
84
  .option('-r, --require <modules...>', 'module(s) to preload')
83
85
  .option('--sandbox', 'enable chromium sand-boxing')
84
- .option('--rich-events', 'Mimics a heartbeat run')
85
- .option('--no-headless', 'run browser in head-full mode')
86
+ .option(
87
+ '--rich-events',
88
+ 'preset flag used when running monitors directly via Heartbeat'
89
+ )
90
+ .option('--no-headless', 'run with the browser in headful mode')
86
91
  .option(
87
92
  '--capability <features...>',
88
93
  'Enable capabilities through feature flags'
@@ -90,7 +95,7 @@ program
90
95
  .addOption(
91
96
  new Option(
92
97
  '--screenshots [flag]',
93
- 'take screenshots at end of each step'
98
+ 'Control whether to capture screenshots at the end of each step'
94
99
  ).choices(['on', 'off', 'only-on-failure'])
95
100
  )
96
101
  .option(
@@ -160,6 +165,7 @@ program
160
165
  .description(
161
166
  'Push all journeys in the current directory to create monitors within the Kibana monitor management UI'
162
167
  )
168
+ .addOption(authMandatory)
163
169
  .option(
164
170
  '--schedule <time-in-minutes>',
165
171
  "schedule in minutes for the pushed monitors. Setting `10`, for example, configures monitors which don't have an interval defined to run every 10 minutes.",
@@ -175,7 +181,7 @@ program
175
181
  '--private-locations <locations...>',
176
182
  'default list of private locations from which your monitors will run.'
177
183
  )
178
- .option('--url <url>', 'Kibana URL to upload the monitors')
184
+ .option('--url <url>', 'Kibana URL to upload the project monitors')
179
185
  .option(
180
186
  '--id <id>',
181
187
  'project id that will be used for logically grouping monitors'
@@ -185,20 +191,23 @@ program
185
191
  'the target Kibana spaces for the pushed monitors — spaces help you organise pushed monitors.'
186
192
  )
187
193
  .option('-y, --yes', 'skip all questions and run non-interactively')
188
- .addOption(authMandatory)
194
+
189
195
  .addOption(pattern)
196
+ .addOption(tags)
197
+ .addOption(match)
190
198
  .addOption(params)
191
199
  .addOption(playwrightOpts)
192
- .action(async (cmdOpts: PushOptions) => {
200
+ .addOption(configOpt)
201
+ .action(async cmdOpts => {
202
+ cmdOpts = { ...cmdOpts, ...program.opts() };
193
203
  const workDir = cwd();
194
- const tearDown = await globalSetup({ inline: false, ...program.opts() }, [
204
+ const tearDown = await globalSetup({ inline: false, ...cmdOpts }, [
195
205
  workDir,
196
206
  ]);
197
207
  try {
198
- const settings = await loadSettings();
208
+ const settings = await loadSettings(cmdOpts.config);
199
209
  const options = (await normalizeOptions(
200
210
  {
201
- ...program.opts(),
202
211
  ...settings,
203
212
  ...cmdOpts,
204
213
  },
@@ -244,8 +253,7 @@ program
244
253
  .addOption(auth)
245
254
  .action(async (cmdOpts: LocationCmdOptions) => {
246
255
  const revert = installTransform();
247
- const url = cmdOpts.url ?? (await loadSettings(true))?.url;
248
-
256
+ const url = cmdOpts.url ?? (await loadSettings(null, true))?.url;
249
257
  try {
250
258
  if (url && cmdOpts.auth) {
251
259
  const allLocations = await getLocations({
@@ -200,13 +200,18 @@ export type ThrottlingOptions = {
200
200
  latency?: number;
201
201
  };
202
202
 
203
+ type GrepOptions = {
204
+ pattern?: string;
205
+ tags?: Array<string>;
206
+ match?: string;
207
+ };
208
+
203
209
  type BaseArgs = {
204
210
  params?: Params;
205
211
  screenshots?: ScreenshotOptions;
206
212
  dryRun?: boolean;
207
- pattern?: string;
208
- match?: string;
209
- tags?: Array<string>;
213
+ config?: string;
214
+ auth?: string;
210
215
  outfd?: number;
211
216
  wsEndpoint?: string;
212
217
  pauseOnError?: boolean;
@@ -219,7 +224,9 @@ type BaseArgs = {
219
224
  };
220
225
 
221
226
  export type CliArgs = BaseArgs & {
222
- config?: string;
227
+ pattern?: string;
228
+ match?: string;
229
+ tags?: Array<string>;
223
230
  reporter?: BuiltInReporterName;
224
231
  inline?: boolean;
225
232
  require?: Array<string>;
@@ -239,6 +246,7 @@ export type RunOptions = BaseArgs & {
239
246
  environment?: string;
240
247
  networkConditions?: NetworkConditions;
241
248
  reporter?: BuiltInReporterName | ReporterInstance;
249
+ grepOpts?: GrepOptions;
242
250
  };
243
251
 
244
252
  export type PushOptions = Partial<ProjectSettings> &
@@ -246,9 +254,11 @@ export type PushOptions = Partial<ProjectSettings> &
246
254
  auth: string;
247
255
  kibanaVersion?: string;
248
256
  yes?: boolean;
257
+ tags?: Array<string>;
249
258
  alert?: AlertConfig;
250
259
  retestOnFailure?: MonitorConfig['retestOnFailure'];
251
260
  enabled?: boolean;
261
+ grepOpts?: GrepOptions;
252
262
  };
253
263
 
254
264
  export type ProjectSettings = {
@@ -404,8 +404,7 @@ export default class Runner {
404
404
 
405
405
  buildMonitors(options: PushOptions) {
406
406
  /**
407
- * Update the global monitor configuration required for
408
- * setting defaults
407
+ * Update the global monitor configuration required for setting defaults
409
408
  */
410
409
  this.updateMonitor({
411
410
  throttling: options.throttling,
@@ -430,11 +429,23 @@ export default class Runner {
430
429
  );
431
430
  }
432
431
  /**
433
- * Execute dummy callback to get all monitor specific
434
- * configurations for the current journey
432
+ * Before pushing a browser monitor, three things need to be done:
433
+ *
434
+ * - execute callback `monitor.use` in particular to get monitor configurations
435
+ * - update the monitor config with global configuration
436
+ * - filter out monitors based on matched tags and name after applying both
437
+ * global and local monitor configurations
435
438
  */
436
439
  journey.callback({ params: options.params } as any);
437
440
  journey.monitor.update(this.monitor?.config);
441
+ if (
442
+ !journey.monitor.isMatch(
443
+ options.grepOpts?.match,
444
+ options.grepOpts?.tags
445
+ )
446
+ ) {
447
+ continue;
448
+ }
438
449
  journey.monitor.validate();
439
450
  monitors.push(journey.monitor);
440
451
  }
@@ -454,7 +465,7 @@ export default class Runner {
454
465
  params: options.params,
455
466
  }).catch(e => (this.hookError = e));
456
467
 
457
- const { dryRun, match, tags } = options;
468
+ const { dryRun, grepOpts } = options;
458
469
  /**
459
470
  * Skip other journeys when using `.only`
460
471
  */
@@ -471,7 +482,7 @@ export default class Runner {
471
482
  this.#reporter.onJourneyRegister?.(journey);
472
483
  continue;
473
484
  }
474
- if (!journey.isMatch(match, tags) || journey.skip) {
485
+ if (!journey.isMatch(grepOpts?.match, grepOpts?.tags) || journey.skip) {
475
486
  continue;
476
487
  }
477
488
  const journeyResult: JourneyResult = this.hookError
@@ -33,7 +33,7 @@ import {
33
33
  Params,
34
34
  PlaywrightOptions,
35
35
  } from '../common_types';
36
- import { indent } from '../helpers';
36
+ import { indent, isMatch } from '../helpers';
37
37
  import { LocationsMap } from '../locations/public-locations';
38
38
 
39
39
  export type SyntheticsLocationsType = keyof typeof LocationsMap;
@@ -129,6 +129,18 @@ export class Monitor {
129
129
  this.filter = filter;
130
130
  }
131
131
 
132
+ /**
133
+ * Matches monitors based on the provided args. Proitize tags over match
134
+ */
135
+ isMatch(matchPattern: string, tagsPattern: Array<string>) {
136
+ return isMatch(
137
+ this.config.tags,
138
+ this.config.name,
139
+ tagsPattern,
140
+ matchPattern
141
+ );
142
+ }
143
+
132
144
  /**
133
145
  * Hash is used to identify if the monitor has changed since the last time
134
146
  * it was pushed to Kibana. Change is based on three factors:
@@ -22,13 +22,14 @@
22
22
  * THE SOFTWARE.
23
23
  *
24
24
  */
25
+ import { URL } from 'url';
25
26
  import { execSync } from 'child_process';
26
27
  import { existsSync } from 'fs';
27
28
  import { mkdir, readFile, writeFile } from 'fs/promises';
28
29
  import { bold, cyan, yellow } from 'kleur/colors';
29
30
  import { join, relative, dirname, basename } from 'path';
30
31
  // @ts-ignore-next-line: has no exported member 'Input'
31
- import { prompt, Input } from 'enquirer';
32
+ import { prompt, Input, Password } from 'enquirer';
32
33
  import { getProjectApiKeyURL, progress, write as stdWrite } from '../helpers';
33
34
  import {
34
35
  getPackageManager,
@@ -60,6 +61,8 @@ export const REGULAR_FILES_PATH = [
60
61
  ];
61
62
  export const CONFIG_PATH = 'synthetics.config.ts';
62
63
 
64
+ const IS_URL = new RegExp('^(https?:\\/\\/)');
65
+
63
66
  export class Generator {
64
67
  pkgManager = 'npm';
65
68
  constructor(public projectDir: string) {}
@@ -80,29 +83,30 @@ export class Generator {
80
83
  return JSON.parse(process.env.TEST_QUESTIONS);
81
84
  }
82
85
 
83
- const { onCloud } = await prompt<{ onCloud: string }>({
84
- type: 'confirm',
85
- name: 'onCloud',
86
- initial: 'y',
87
- message: 'Do you use Elastic Cloud',
88
- });
89
86
  const url = await new Input({
90
- header: onCloud
91
- ? yellow(
92
- 'Get cloud.id from your deployment https://www.elastic.co/guide/en/cloud/current/ec-cloud-id.html'
93
- )
94
- : '',
95
- message: onCloud
96
- ? 'What is your cloud.id'
97
- : 'What is the url of your Kibana instance',
98
87
  name: 'url',
88
+ message: 'Enter Elastic Kibana URL or Cloud ID',
99
89
  required: true,
100
- result(value) {
101
- return onCloud ? cloudIDToKibanaURL(value) : value;
90
+ validate(value) {
91
+ try {
92
+ if (!IS_URL.test(value)) {
93
+ value = cloudIDToKibanaURL(value);
94
+ }
95
+ new URL(value);
96
+ return true;
97
+ } catch (e) {
98
+ return 'Invalid URL or Cloud ID';
99
+ }
100
+ },
101
+ result(value: string) {
102
+ if (!IS_URL.test(value)) {
103
+ value = cloudIDToKibanaURL(value);
104
+ }
105
+ return value;
102
106
  },
103
107
  }).run();
104
108
 
105
- const auth = await new Input({
109
+ const auth = await new Password({
106
110
  name: 'auth',
107
111
  header: yellow(
108
112
  `Generate API key from Kibana ${getProjectApiKeyURL(url)}`
@@ -249,7 +253,7 @@ export class Generator {
249
253
  }
250
254
  // Add push command
251
255
  if (!pkgJSON.scripts.push) {
252
- pkgJSON.scripts.push = 'npx @elastic/synthetics push';
256
+ pkgJSON.scripts.push = `npx @elastic/synthetics push`;
253
257
  }
254
258
 
255
259
  await this.createFile(
@@ -287,7 +291,7 @@ All set, you can run below commands inside: ${this.projectDir}:
287
291
  )}
288
292
 
289
293
  ${yellow(
290
- 'Make sure to configure the SYNTHETICS_API_KEY before pushing monitors to Kibana.'
294
+ 'Configure API Key via `SYNTHETICS_API_KEY` env variable or --auth CLI flag.'
291
295
  )}
292
296
 
293
297
  Visit https://www.elastic.co/guide/en/observability/current/synthetic-run-tests.html to learn more.
package/src/options.ts CHANGED
@@ -26,74 +26,49 @@
26
26
  import merge from 'deepmerge';
27
27
  import { createOption } from 'commander';
28
28
  import { readConfig } from './config';
29
- import type { CliArgs, PushOptions, RunOptions } from './common_types';
30
- import { THROTTLING_WARNING_MSG, error, warn } from './helpers';
29
+ import type { CliArgs, RunOptions } from './common_types';
30
+ import { THROTTLING_WARNING_MSG, warn } from './helpers';
31
31
 
32
32
  type Mode = 'run' | 'push';
33
33
 
34
+ /**
35
+ * Normalize the options passed via CLI and Synthetics config file
36
+ *
37
+ * Order of preference for options:
38
+ * 1. Local options configured via Runner API
39
+ * 2. CLI flags
40
+ * 3. Configuration file
41
+ */
34
42
  export async function normalizeOptions(
35
43
  cliArgs: CliArgs,
36
44
  mode: Mode = 'run'
37
45
  ): Promise<RunOptions> {
46
+ /**
47
+ * Move filtering flags from the top level to filter object
48
+ * and delete the old keys
49
+ */
50
+ const grepOpts = {
51
+ pattern: cliArgs.pattern,
52
+ tags: cliArgs.tags,
53
+ match: cliArgs.match,
54
+ };
55
+ delete cliArgs.pattern;
56
+ delete cliArgs.tags;
57
+ delete cliArgs.match;
58
+
38
59
  const options: RunOptions = {
39
60
  ...cliArgs,
61
+ grepOpts,
40
62
  environment: process.env['NODE_ENV'] || 'development',
41
63
  };
42
64
  /**
43
- * Group all events that can be consumed by heartbeat and
44
- * eventually by the Synthetics UI.
45
- */
46
- if (cliArgs.richEvents) {
47
- options.reporter = cliArgs.reporter ?? 'json';
48
- options.ssblocks = true;
49
- options.network = true;
50
- options.trace = true;
51
- options.quietExitCode = true;
52
- }
53
-
54
- if (cliArgs.capability) {
55
- const supportedCapabilities = [
56
- 'trace',
57
- 'network',
58
- 'filmstrips',
59
- 'metrics',
60
- 'ssblocks',
61
- ];
62
- /**
63
- * trace - record chrome trace events(LCP, FCP, CLS, etc.) for all journeys
64
- * network - capture network information for all journeys
65
- * filmstrips - record detailed filmstrips for all journeys
66
- * metrics - capture performance metrics (DOM Nodes, Heap size, etc.) for each step
67
- * ssblocks - Dedupes the screenshots in to blocks to save storage space
68
- */
69
- for (const flag of cliArgs.capability) {
70
- if (supportedCapabilities.includes(flag)) {
71
- options[flag] = true;
72
- } else {
73
- console.warn(
74
- `Missing capability "${flag}", current supported capabilities are ${supportedCapabilities.join(
75
- ', '
76
- )}`
77
- );
78
- }
79
- }
80
- }
81
-
82
- /**
83
- * Validate and read synthetics config file
84
- * based on the environment
65
+ * Validate and read synthetics config file based on the environment
85
66
  */
86
67
  const config =
87
68
  cliArgs.config || !cliArgs.inline
88
69
  ? await readConfig(options.environment, cliArgs.config)
89
70
  : {};
90
71
 
91
- /**
92
- * Order of preference for options that are used while running are
93
- * 1. Local options configured via Runner API
94
- * 2. CLI flags
95
- * 3. Configuration file
96
- */
97
72
  options.params = Object.freeze(merge(config.params, cliArgs.params || {}));
98
73
 
99
74
  /**
@@ -118,10 +93,49 @@ export async function normalizeOptions(
118
93
  */
119
94
  switch (mode) {
120
95
  case 'run':
96
+ if (cliArgs.capability) {
97
+ const supportedCapabilities = [
98
+ 'trace',
99
+ 'network',
100
+ 'filmstrips',
101
+ 'metrics',
102
+ 'ssblocks',
103
+ ];
104
+ /**
105
+ * trace - record chrome trace events(LCP, FCP, CLS, etc.) for all journeys
106
+ * network - capture network information for all journeys
107
+ * filmstrips - record detailed filmstrips for all journeys
108
+ * metrics - capture performance metrics (DOM Nodes, Heap size, etc.) for each step
109
+ * ssblocks - Dedupes the screenshots in to blocks to save storage space
110
+ */
111
+ for (const flag of cliArgs.capability) {
112
+ if (supportedCapabilities.includes(flag)) {
113
+ options[flag] = true;
114
+ } else {
115
+ console.warn(
116
+ `Missing capability "${flag}", current supported capabilities are ${supportedCapabilities.join(
117
+ ', '
118
+ )}`
119
+ );
120
+ }
121
+ }
122
+ }
123
+
124
+ /**
125
+ * Group all events that can be consumed by heartbeat and
126
+ * eventually by the Synthetics UI.
127
+ */
128
+ if (cliArgs.richEvents) {
129
+ options.reporter = cliArgs.reporter ?? 'json';
130
+ options.ssblocks = true;
131
+ options.network = true;
132
+ options.trace = true;
133
+ options.quietExitCode = true;
134
+ }
135
+
121
136
  options.screenshots = cliArgs.screenshots ?? 'on';
122
137
  break;
123
138
  case 'push':
124
- validatePushOptions(options as PushOptions);
125
139
  /**
126
140
  * Merge the default monitor config from synthetics.config.ts file
127
141
  * with the CLI options passed via push command
@@ -152,14 +166,6 @@ export function getHeadlessFlag(
152
166
  return configHeadless ?? true;
153
167
  }
154
168
 
155
- export function validatePushOptions(opts: PushOptions) {
156
- if (opts.tags || opts.match) {
157
- throw error(`Aborted. Invalid CLI flags.
158
-
159
- Tags and Match are not supported in push command.`);
160
- }
161
- }
162
-
163
169
  /* eslint-disable-next-line @typescript-eslint/no-unused-vars */
164
170
  function toObject(value: boolean | Record<string, any>): Record<string, any> {
165
171
  const defaulVal = {};
@@ -183,19 +189,17 @@ export function parseThrottling() {
183
189
  export function getCommonCommandOpts() {
184
190
  const params = createOption(
185
191
  '-p, --params <jsonstring>',
186
- 'JSON object that gets injected to all journeys'
187
- );
188
- params.argParser(JSON.parse);
192
+ 'JSON object that defines any variables your tests require.'
193
+ ).argParser(JSON.parse);
189
194
 
190
195
  const playwrightOpts = createOption(
191
196
  '--playwright-options <jsonstring>',
192
197
  'JSON object to pass in custom Playwright options for the agent. Options passed will be merged with Playwright options defined in your synthetics.config.js file.'
193
- );
194
- playwrightOpts.argParser(JSON.parse);
198
+ ).argParser(JSON.parse);
195
199
 
196
200
  const pattern = createOption(
197
201
  '--pattern <pattern>',
198
- 'RegExp pattern to match journey/monitor files that are different from the default (ex: /*.journey.(ts|js)$/)'
202
+ 'RegExp pattern to match journey files in the current working directory (default: /*.journey.(ts|js)$/)'
199
203
  );
200
204
 
201
205
  const apiDocsLink =
@@ -208,11 +212,28 @@ export function getCommonCommandOpts() {
208
212
  .env('SYNTHETICS_API_KEY')
209
213
  .makeOptionMandatory(true);
210
214
 
215
+ const configOpt = createOption(
216
+ '-c, --config <path>',
217
+ 'path to the configuration file (default: synthetics.config.(js|ts))'
218
+ );
219
+
220
+ const tags = createOption(
221
+ '--tags <name...>',
222
+ 'run/push tests with the tag(s) matching a pattern'
223
+ );
224
+ const match = createOption(
225
+ '--match <name>',
226
+ 'run/push tests with a name or tags that matches a pattern'
227
+ );
228
+
211
229
  return {
212
230
  auth,
213
231
  authMandatory,
214
232
  params,
215
233
  playwrightOpts,
216
234
  pattern,
235
+ configOpt,
236
+ tags,
237
+ match,
217
238
  };
218
239
  }
package/src/push/index.ts CHANGED
@@ -58,13 +58,16 @@ import {
58
58
  isLightweightMonitorSupported,
59
59
  logDiff,
60
60
  } from './utils';
61
+ import { log } from '../core/logger';
61
62
 
62
63
  export async function push(monitors: Monitor[], options: PushOptions) {
63
64
  const duplicates = trackDuplicates(monitors);
64
65
  if (duplicates.size > 0) {
65
66
  throw error(formatDuplicateError(duplicates));
66
67
  }
67
- progress(`Pushing monitors for project: ${options.id}`);
68
+ progress(
69
+ `Pushing monitors for '${options.id}' project in kibana '${options.space}' space`
70
+ );
68
71
 
69
72
  /**
70
73
  * Legacy API for kibana which does not support bulk operations
@@ -97,8 +100,17 @@ export async function push(monitors: Monitor[], options: PushOptions) {
97
100
  }
98
101
 
99
102
  if (removedIDs.size > 0) {
103
+ log(`deleting monitor ids: ${Array.from(removedIDs.keys()).join(', ')}`);
100
104
  if (updatedMonitors.size === 0 && unchangedIDs.size === 0) {
101
- await promptConfirmDeleteAll(options);
105
+ await confirmDelete(
106
+ `Pushing without any monitors will delete all monitors associated with the project.\n Do you want to continue?`,
107
+ options.yes
108
+ );
109
+ } else {
110
+ await confirmDelete(
111
+ `Deleting ${removedIDs.size} monitors. Do you want to continue?`,
112
+ options.yes
113
+ );
102
114
  }
103
115
  const chunks = getChunks(Array.from(removedIDs), CHUNK_SIZE);
104
116
  for (const chunk of chunks) {
@@ -112,22 +124,22 @@ export async function push(monitors: Monitor[], options: PushOptions) {
112
124
  done(`Pushed: ${grey(getMonitorManagementURL(options.url))}`);
113
125
  }
114
126
 
115
- async function promptConfirmDeleteAll(options: PushOptions) {
116
- write('');
117
- const { deleteAll } = await prompt<{ deleteAll: boolean }>({
127
+ async function confirmDelete(message: string, skip: boolean) {
128
+ const { deleteMonitors } = await prompt<{ deleteMonitors: boolean }>({
118
129
  type: 'confirm',
119
130
  skip() {
120
- if (options.yes) {
131
+ write('');
132
+ if (skip || !process.stdout.isTTY) {
121
133
  this.initial = process.env.TEST_OVERRIDE ?? true;
122
134
  return true;
123
135
  }
124
136
  return false;
125
137
  },
126
- name: 'deleteAll',
127
- message: `Pushing without any monitors will delete all monitors associated with the project.\n Do you want to continue?`,
138
+ name: 'deleteMonitors',
139
+ message,
128
140
  initial: false,
129
141
  });
130
- if (!deleteAll) {
142
+ if (!deleteMonitors) {
131
143
  throw warn('Push command Aborted');
132
144
  }
133
145
  }
@@ -160,9 +172,12 @@ export function formatDuplicateError(monitors: Set<Monitor>) {
160
172
 
161
173
  const INSTALLATION_HELP = `Run 'npx @elastic/synthetics init' to create project with default settings.`;
162
174
 
163
- export async function loadSettings(ignoreMissing = false) {
175
+ export async function loadSettings(configPath, ignoreMissing = false) {
164
176
  try {
165
- const config = await readConfig(process.env['NODE_ENV'] || 'development');
177
+ const config = await readConfig(
178
+ process.env['NODE_ENV'] || 'development',
179
+ configPath
180
+ );
166
181
  // Missing config file, fake throw to capture as missing file
167
182
  if (Object.keys(config).length === 0) {
168
183
  throw '';
@@ -216,9 +231,13 @@ ${reason}
216
231
  ${INSTALLATION_HELP}`);
217
232
  }
218
233
 
219
- async function overrideSettings(oldValue: string, newValue: string) {
234
+ async function overrideSettings(
235
+ configPath,
236
+ oldValue: string,
237
+ newValue: string
238
+ ) {
220
239
  const cwd = process.cwd();
221
- const configPath = await findSyntheticsConfig(cwd, cwd);
240
+ configPath = configPath ?? (await findSyntheticsConfig(cwd, cwd));
222
241
  if (!configPath) {
223
242
  throw warn(`Unable to find synthetics config file: ${configPath}`);
224
243
  }
@@ -256,7 +275,7 @@ export async function catchIncorrectSettings(
256
275
  }
257
276
  }
258
277
  if (override) {
259
- await overrideSettings(settings.id, options.id);
278
+ await overrideSettings(options.config, settings.id, options.id);
260
279
  }
261
280
  }
262
281
 
@@ -279,7 +298,10 @@ export async function pushLegacy(monitors: Monitor[], options: PushOptions) {
279
298
  );
280
299
  }
281
300
  } else {
282
- await promptConfirmDeleteAll(options);
301
+ await confirmDelete(
302
+ `Pushing without any monitors will delete all monitors associated with the project.\n Do you want to continue?`,
303
+ options.yes
304
+ );
283
305
  }
284
306
  await liveProgress(
285
307
  createMonitorsLegacy({ schemas, keepStale: false, options }),