@appliqation/automation-sdk 2.4.0 → 2.5.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/README.md +11 -60
- package/package.json +1 -1
- package/src/AppliqationClient.js +125 -467
- package/src/core/HttpClient.js +0 -62
- package/src/playwright/global-setup.js +44 -153
- package/src/reporters/cypress/CypressReporter.js +0 -12
- package/src/reporters/jest/JestReporter.js +0 -12
- package/src/reporters/playwright/AppliqationReporter.js +398 -968
- package/src/services/ProjectInfoService.js +44 -0
package/src/core/HttpClient.js
CHANGED
|
@@ -306,68 +306,6 @@ class HttpClient {
|
|
|
306
306
|
}
|
|
307
307
|
}
|
|
308
308
|
|
|
309
|
-
/**
|
|
310
|
-
* Fetch project information from API key
|
|
311
|
-
* Enables SDK auto-configuration where only API key is required.
|
|
312
|
-
* Returns project metadata including ID, title, and configured environments.
|
|
313
|
-
*
|
|
314
|
-
* @returns {Promise<Object>} Project metadata
|
|
315
|
-
* @property {boolean} success - Operation success status
|
|
316
|
-
* @property {number} project_id - Numeric project ID
|
|
317
|
-
* @property {number} project_nid - Same as project_id (for consistency)
|
|
318
|
-
* @property {string} project_title - Project name
|
|
319
|
-
* @property {Array<string>} environments - List of configured environment names
|
|
320
|
-
* @property {string} default_environment - First environment or 'Local' fallback
|
|
321
|
-
* @property {boolean} has_environments_configured - Whether environments exist
|
|
322
|
-
*
|
|
323
|
-
* @throws {Error} If API key is invalid or project not found
|
|
324
|
-
*
|
|
325
|
-
* @example
|
|
326
|
-
* const projectInfo = await httpClient.getProjectInfo();
|
|
327
|
-
* console.log(projectInfo.project_id); // 1162
|
|
328
|
-
* console.log(projectInfo.environments); // ['Development', 'Staging', 'Production']
|
|
329
|
-
* console.log(projectInfo.default_environment); // 'Development'
|
|
330
|
-
*/
|
|
331
|
-
async getProjectInfo() {
|
|
332
|
-
logger.debug('Fetching project information from API key...');
|
|
333
|
-
|
|
334
|
-
try {
|
|
335
|
-
const response = await this.get('/api/automation/project/info');
|
|
336
|
-
|
|
337
|
-
logger.info('Project info retrieved successfully', {
|
|
338
|
-
project_id: response.data.project_id,
|
|
339
|
-
project_title: response.data.project_title,
|
|
340
|
-
environments: response.data.environments,
|
|
341
|
-
has_environments: response.data.has_environments_configured
|
|
342
|
-
});
|
|
343
|
-
|
|
344
|
-
return response.data;
|
|
345
|
-
} catch (error) {
|
|
346
|
-
logger.error('Failed to fetch project info', {
|
|
347
|
-
error: error.message,
|
|
348
|
-
status: error.response?.status,
|
|
349
|
-
code: error.code
|
|
350
|
-
});
|
|
351
|
-
|
|
352
|
-
// Enhance error message for common scenarios
|
|
353
|
-
if (error.response?.status === 400) {
|
|
354
|
-
throw new Error(
|
|
355
|
-
'No project associated with this API key. Please verify your API key is correctly configured.'
|
|
356
|
-
);
|
|
357
|
-
} else if (error.response?.status === 404) {
|
|
358
|
-
throw new Error(
|
|
359
|
-
'Project not found or invalid. Please verify your API key is associated with a valid project.'
|
|
360
|
-
);
|
|
361
|
-
} else if (error.response?.status === 401 || error.response?.status === 403) {
|
|
362
|
-
throw new Error(
|
|
363
|
-
'API key authentication failed. Please verify your API key is valid and not expired.'
|
|
364
|
-
);
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
throw error;
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
|
|
371
309
|
/**
|
|
372
310
|
* Set authorization header
|
|
373
311
|
* @param {string} token - Authorization token
|
|
@@ -1,170 +1,93 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Global Setup for JWT Browser Authentication
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* How it works:
|
|
8
|
-
* 1. Uses API Key to call /api/automation/run/create
|
|
9
|
-
* 2. Receives JWT token from backend
|
|
10
|
-
* 3. Navigates browser to app with ?qonsole_token={jwt}
|
|
11
|
-
* 4. Backend validates JWT and establishes session
|
|
12
|
-
* 5. Saves authenticated state to .auth/jwt.json
|
|
13
|
-
* 6. All tests use this authenticated state
|
|
14
|
-
*
|
|
15
|
-
* Benefits:
|
|
16
|
-
* - No username/password in .env
|
|
17
|
-
* - No manual setup step required
|
|
18
|
-
* - 1-hour token validity
|
|
19
|
-
* - Auto-refresh support
|
|
20
|
-
* - Works in CI/CD environments
|
|
4
|
+
* Enabled only when --appq CLI flag is present.
|
|
5
|
+
* Never blocks Playwright test execution — all errors degrade to warnings.
|
|
21
6
|
*
|
|
22
7
|
* @module playwright/global-setup
|
|
23
8
|
*/
|
|
24
9
|
|
|
25
|
-
|
|
10
|
+
// Lazy-require @playwright/test to avoid double-load when SDK is symlinked
|
|
26
11
|
const axios = require('axios');
|
|
27
12
|
const https = require('https');
|
|
28
13
|
const fs = require('fs');
|
|
29
14
|
const path = require('path');
|
|
30
15
|
const { DEFAULT_APPLIQATION_BASE_URL } = require('../constants');
|
|
31
16
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
return undefined;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function parseCliEnableFlag() {
|
|
42
|
-
const argv = process.argv || [];
|
|
43
|
-
for (let i = 0; i < argv.length; i++) {
|
|
44
|
-
const arg = argv[i];
|
|
45
|
-
if (arg.startsWith('--appq=')) {
|
|
46
|
-
return toBoolean(arg.split('=')[1]);
|
|
47
|
-
}
|
|
48
|
-
if (arg === '--appq') {
|
|
49
|
-
return true;
|
|
50
|
-
}
|
|
51
|
-
if (arg.startsWith('--enable-appq=')) {
|
|
52
|
-
return toBoolean(arg.split('=')[1]);
|
|
53
|
-
}
|
|
54
|
-
if (arg === '--enable-appq') {
|
|
55
|
-
return true;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
return undefined;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function resolveEnableAppq(config) {
|
|
62
|
-
const cliValue = parseCliEnableFlag();
|
|
63
|
-
const envValue = process.env.APPLIQATION_ENABLE ?? process.env.APPQ ?? process.env.APPLIQATION_APPQ;
|
|
64
|
-
const configValue = config?.enableAppq ?? config?.options?.enableAppq;
|
|
65
|
-
const resolved = toBoolean(configValue) ?? toBoolean(cliValue) ?? toBoolean(envValue);
|
|
66
|
-
return resolved !== undefined ? resolved : true;
|
|
17
|
+
/**
|
|
18
|
+
* Check if --appq flag is present in CLI args
|
|
19
|
+
* @returns {boolean}
|
|
20
|
+
*/
|
|
21
|
+
function isAppqEnabled() {
|
|
22
|
+
return (process.argv || []).includes('--appq');
|
|
67
23
|
}
|
|
68
24
|
|
|
69
25
|
/**
|
|
70
26
|
* Setup JWT-based browser authentication
|
|
71
|
-
*
|
|
72
27
|
* @param {Object} config - Playwright config object
|
|
73
28
|
* @returns {Promise<void>}
|
|
74
29
|
*/
|
|
75
30
|
async function globalSetup(config) {
|
|
76
|
-
console.log('\n
|
|
77
|
-
|
|
78
|
-
// Extract SDK configuration from reporter config or use env
|
|
79
|
-
const sdkConfig = extractSDKConfig(config);
|
|
80
|
-
sdkConfig.enableAppq = resolveEnableAppq(sdkConfig);
|
|
81
|
-
|
|
82
|
-
// Display startup information
|
|
83
|
-
console.log(`🌍 Environment: ${sdkConfig.environment || 'Not specified'}`);
|
|
84
|
-
console.log(`🔗 Test App URL: ${sdkConfig.appUrl || 'Not specified'}`);
|
|
31
|
+
console.log('\n[Appliqation] Global Setup: Initializing...\n');
|
|
85
32
|
|
|
86
|
-
if
|
|
87
|
-
|
|
33
|
+
// Only run if --appq flag is present
|
|
34
|
+
if (!isAppqEnabled()) {
|
|
35
|
+
console.log('[Appliqation] Reporting disabled. Skipping JWT setup.\n');
|
|
88
36
|
return;
|
|
89
37
|
}
|
|
90
38
|
|
|
91
|
-
|
|
92
|
-
if (process.env.LOG_LEVEL === 'DEBUG') {
|
|
93
|
-
const crypto = require('crypto');
|
|
94
|
-
const runId = crypto.randomUUID();
|
|
95
|
-
console.log(`📊 Run ID: ${runId}`);
|
|
96
|
-
console.log(`⏰ Timestamp: ${new Date().toISOString()}`);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
console.log(`🔑 API Key: ${sdkConfig.apiKey ? 'Configured' : 'Not set'}\n`);
|
|
39
|
+
const sdkConfig = extractSDKConfig(config);
|
|
100
40
|
|
|
101
|
-
console.log(
|
|
41
|
+
console.log(`[Appliqation] Environment: ${sdkConfig.environment || 'Not specified'}`);
|
|
102
42
|
|
|
103
|
-
// Validate
|
|
43
|
+
// Validate API key — warn, don't throw
|
|
104
44
|
if (!sdkConfig.apiKey) {
|
|
105
|
-
console.
|
|
106
|
-
console.
|
|
107
|
-
|
|
108
|
-
throw new Error('Missing APPLIQATION_API_KEY');
|
|
45
|
+
console.warn('[Appliqation] API key required. Set APPLIQATION_API_KEY in .env');
|
|
46
|
+
console.warn('[Appliqation] Tests will continue without JWT auth.\n');
|
|
47
|
+
return;
|
|
109
48
|
}
|
|
110
49
|
|
|
111
|
-
|
|
112
|
-
console.error('❌ APPLIQATION_BASE_URL is required');
|
|
113
|
-
throw new Error('Missing APPLIQATION_BASE_URL');
|
|
114
|
-
}
|
|
50
|
+
console.log('[Appliqation] API Key: Configured');
|
|
115
51
|
|
|
116
52
|
// Determine if JWT auth is enabled
|
|
117
|
-
const useJwtAuth = sdkConfig.useJwtAuth !== false;
|
|
53
|
+
const useJwtAuth = sdkConfig.useJwtAuth !== false;
|
|
118
54
|
|
|
119
55
|
if (!useJwtAuth) {
|
|
120
|
-
console.log('
|
|
56
|
+
console.log('[Appliqation] JWT authentication disabled.\n');
|
|
121
57
|
return;
|
|
122
58
|
}
|
|
123
59
|
|
|
124
60
|
try {
|
|
125
|
-
|
|
126
|
-
console.log('📡 Requesting JWT token from Appliqation backend...');
|
|
61
|
+
console.log('[Appliqation] Requesting JWT token...');
|
|
127
62
|
const jwtData = await getJWTToken(sdkConfig);
|
|
128
63
|
|
|
129
64
|
if (!jwtData.jwt_token) {
|
|
130
|
-
console.warn('
|
|
65
|
+
console.warn('[Appliqation] No JWT token received. Tests will continue without auth.\n');
|
|
131
66
|
return;
|
|
132
67
|
}
|
|
133
68
|
|
|
134
|
-
console.log(
|
|
69
|
+
console.log('[Appliqation] JWT token received.');
|
|
135
70
|
|
|
136
|
-
|
|
137
|
-
console.log('🌐 Setting up browser session with JWT...');
|
|
71
|
+
console.log('[Appliqation] Setting up browser session...');
|
|
138
72
|
await setupBrowserWithJWT(sdkConfig, jwtData.jwt_token);
|
|
139
73
|
|
|
140
|
-
console.log('
|
|
141
|
-
console.log('
|
|
142
|
-
console.log('✨ Tests will run with JWT authentication!\n');
|
|
74
|
+
console.log('[Appliqation] Browser authentication complete.');
|
|
75
|
+
console.log('[Appliqation] State saved to .auth/jwt.json\n');
|
|
143
76
|
|
|
144
|
-
// Store for use in tests (run_id will be set by reporter when tests execute)
|
|
145
77
|
process.env.APPLIQATION_JWT_TOKEN = jwtData.jwt_token;
|
|
146
78
|
|
|
147
79
|
} catch (error) {
|
|
148
|
-
console.
|
|
149
|
-
console.
|
|
150
|
-
console.error(' 1. Check APPLIQATION_API_KEY is valid');
|
|
151
|
-
console.error(' 2. Check APPLIQATION_BASE_URL is accessible');
|
|
152
|
-
console.error(' 3. Check backend has QONSOLE_JWT_SECRET configured');
|
|
153
|
-
console.error(' 4. Try running: npm run setup-auth (legacy method)\n');
|
|
154
|
-
|
|
155
|
-
// Don't fail the test run - fall back to legacy auth
|
|
156
|
-
console.warn('⚠️ Falling back to legacy authentication method\n');
|
|
80
|
+
console.warn(`[Appliqation] Auth setup failed: ${error.message}`);
|
|
81
|
+
console.warn('[Appliqation] Tests will continue without JWT auth.\n');
|
|
157
82
|
}
|
|
158
83
|
}
|
|
159
84
|
|
|
160
85
|
/**
|
|
161
86
|
* Extract SDK configuration from Playwright config
|
|
162
|
-
*
|
|
163
87
|
* @param {Object} config - Playwright config
|
|
164
88
|
* @returns {Object} SDK configuration
|
|
165
89
|
*/
|
|
166
90
|
function extractSDKConfig(config) {
|
|
167
|
-
// Try to find SDK config in reporter configuration
|
|
168
91
|
let sdkConfig = {};
|
|
169
92
|
|
|
170
93
|
if (config.reporter) {
|
|
@@ -181,15 +104,14 @@ function extractSDKConfig(config) {
|
|
|
181
104
|
}
|
|
182
105
|
}
|
|
183
106
|
|
|
184
|
-
|
|
185
|
-
const baseUrl = sdkConfig.baseUrl || process.env.APPLIQATION_BASE_URL || DEFAULT_APPLIQATION_BASE_URL;
|
|
107
|
+
const baseUrl = sdkConfig.baseUrl || DEFAULT_APPLIQATION_BASE_URL;
|
|
186
108
|
|
|
187
109
|
return {
|
|
188
110
|
baseUrl,
|
|
189
111
|
apiKey: sdkConfig.apiKey || process.env.APPLIQATION_API_KEY,
|
|
190
112
|
projectKey: sdkConfig.projectKey || process.env.APPLIQATION_PROJECT_KEY,
|
|
191
113
|
scenarioId: sdkConfig.scenarioId || parseInt(process.env.APPLIQATION_SCENARIO_ID) || 0,
|
|
192
|
-
environment: sdkConfig.environment || process.env.APPLIQATION_ENVIRONMENT
|
|
114
|
+
environment: sdkConfig.environment || process.env.APPLIQATION_ENVIRONMENT,
|
|
193
115
|
appUrl: sdkConfig.appUrl || process.env.APPLIQATION_APP_URL || process.env.TEST_APP_URL || baseUrl,
|
|
194
116
|
useJwtAuth: sdkConfig.useJwtAuth !== undefined ? sdkConfig.useJwtAuth : process.env.APPLIQATION_USE_JWT_AUTH !== 'false',
|
|
195
117
|
runTitle: sdkConfig.runTitle || process.env.APPLIQATION_RUN_TITLE || `JWT Auth Setup - ${new Date().toISOString()}`,
|
|
@@ -198,13 +120,10 @@ function extractSDKConfig(config) {
|
|
|
198
120
|
|
|
199
121
|
/**
|
|
200
122
|
* Get JWT token from Appliqation API
|
|
201
|
-
*
|
|
202
123
|
* @param {Object} sdkConfig - SDK configuration
|
|
203
|
-
* @returns {Promise<Object>} JWT data
|
|
124
|
+
* @returns {Promise<Object>} JWT data
|
|
204
125
|
*/
|
|
205
126
|
async function getJWTToken(sdkConfig) {
|
|
206
|
-
// Get browser session JWT token directly (no need to create run matrix)
|
|
207
|
-
// The SDK reporter will create run matrices automatically when tests execute
|
|
208
127
|
const jwtEndpoint = `${sdkConfig.baseUrl.replace(/\/$/, '')}/api/auth/jwt/browser`;
|
|
209
128
|
|
|
210
129
|
const jwtPayload = {
|
|
@@ -213,12 +132,8 @@ async function getJWTToken(sdkConfig) {
|
|
|
213
132
|
|
|
214
133
|
try {
|
|
215
134
|
const jwtResponse = await axios.post(jwtEndpoint, jwtPayload, {
|
|
216
|
-
headers: {
|
|
217
|
-
|
|
218
|
-
},
|
|
219
|
-
httpsAgent: new https.Agent({
|
|
220
|
-
rejectUnauthorized: false, // Allow self-signed certs in dev
|
|
221
|
-
}),
|
|
135
|
+
headers: { 'Content-Type': 'application/json' },
|
|
136
|
+
httpsAgent: new https.Agent({ rejectUnauthorized: false }),
|
|
222
137
|
validateStatus: (status) => status < 500,
|
|
223
138
|
});
|
|
224
139
|
|
|
@@ -228,12 +143,11 @@ async function getJWTToken(sdkConfig) {
|
|
|
228
143
|
|
|
229
144
|
return {
|
|
230
145
|
jwt_token: jwtResponse.data.jwt_token,
|
|
231
|
-
run_id: null,
|
|
146
|
+
run_id: null,
|
|
232
147
|
user_id: jwtResponse.data.user_id,
|
|
233
148
|
project_id: jwtResponse.data.project_id,
|
|
234
149
|
expires_in: jwtResponse.data.expires_in,
|
|
235
150
|
};
|
|
236
|
-
|
|
237
151
|
} catch (error) {
|
|
238
152
|
if (error.response) {
|
|
239
153
|
throw new Error(`API error: ${error.response.status} - ${error.response.data?.message || error.message}`);
|
|
@@ -244,69 +158,46 @@ async function getJWTToken(sdkConfig) {
|
|
|
244
158
|
|
|
245
159
|
/**
|
|
246
160
|
* Setup browser session with JWT token
|
|
247
|
-
*
|
|
248
|
-
* @param {
|
|
249
|
-
* @param {string} jwtToken - JWT token
|
|
250
|
-
* @returns {Promise<void>}
|
|
161
|
+
* @param {Object} sdkConfig
|
|
162
|
+
* @param {string} jwtToken
|
|
251
163
|
*/
|
|
252
164
|
async function setupBrowserWithJWT(sdkConfig, jwtToken) {
|
|
253
|
-
const
|
|
254
|
-
|
|
255
|
-
});
|
|
165
|
+
const { chromium } = require('@playwright/test');
|
|
166
|
+
const browser = await chromium.launch({ headless: true });
|
|
256
167
|
|
|
257
168
|
try {
|
|
258
|
-
// Extract domain from baseUrl for cookie
|
|
259
169
|
const baseUrl = sdkConfig.baseUrl.replace(/\/$/, '');
|
|
260
170
|
const urlObj = new URL(baseUrl);
|
|
261
171
|
const domain = urlObj.hostname;
|
|
262
172
|
|
|
263
|
-
|
|
264
|
-
const context = await browser.newContext({
|
|
265
|
-
ignoreHTTPSErrors: true, // Allow self-signed certs in dev
|
|
266
|
-
});
|
|
173
|
+
const context = await browser.newContext({ ignoreHTTPSErrors: true });
|
|
267
174
|
|
|
268
|
-
// Set JWT cookie
|
|
269
175
|
await context.addCookies([{
|
|
270
176
|
name: 'appliqation_jwt',
|
|
271
177
|
value: jwtToken,
|
|
272
|
-
domain
|
|
178
|
+
domain,
|
|
273
179
|
path: '/',
|
|
274
180
|
httpOnly: true,
|
|
275
181
|
secure: baseUrl.startsWith('https'),
|
|
276
182
|
sameSite: 'Lax'
|
|
277
183
|
}]);
|
|
278
184
|
|
|
279
|
-
console.log(` ✓ JWT cookie set for domain: ${domain}`);
|
|
280
|
-
|
|
281
185
|
const page = await context.newPage();
|
|
282
186
|
|
|
283
|
-
|
|
284
|
-
console.log(` Navigating to: ${baseUrl}...`);
|
|
285
|
-
await page.goto(baseUrl, {
|
|
286
|
-
waitUntil: 'networkidle',
|
|
287
|
-
timeout: 30000,
|
|
288
|
-
});
|
|
289
|
-
|
|
290
|
-
// Wait for authentication to be processed
|
|
187
|
+
await page.goto(baseUrl, { waitUntil: 'networkidle', timeout: 30000 });
|
|
291
188
|
await page.waitForTimeout(2000);
|
|
292
189
|
|
|
293
|
-
// Verify authentication worked
|
|
294
190
|
const currentUrl = page.url();
|
|
295
191
|
if (currentUrl.includes('/user/login')) {
|
|
296
|
-
throw new Error('JWT authentication failed
|
|
192
|
+
throw new Error('JWT authentication failed — redirected to login page');
|
|
297
193
|
}
|
|
298
194
|
|
|
299
|
-
console.log(' ✓ Browser authenticated successfully');
|
|
300
|
-
|
|
301
|
-
// Save authenticated state with cookie
|
|
302
195
|
const authDir = path.resolve('.auth');
|
|
303
196
|
if (!fs.existsSync(authDir)) {
|
|
304
197
|
fs.mkdirSync(authDir, { recursive: true });
|
|
305
198
|
}
|
|
306
199
|
|
|
307
200
|
await context.storageState({ path: '.auth/jwt.json' });
|
|
308
|
-
console.log(' ✓ Authenticated state saved to .auth/jwt.json');
|
|
309
|
-
|
|
310
201
|
} catch (error) {
|
|
311
202
|
throw new Error(`Browser setup failed: ${error.message}`);
|
|
312
203
|
} finally {
|
|
@@ -161,18 +161,6 @@ class CypressReporter {
|
|
|
161
161
|
*/
|
|
162
162
|
async createRun(details) {
|
|
163
163
|
try {
|
|
164
|
-
// Auto-configure project from API key if needed
|
|
165
|
-
await this.client.ensureProjectConfig();
|
|
166
|
-
|
|
167
|
-
// Use auto-detected environment if not explicitly provided
|
|
168
|
-
if (!this.config.environment && this.client._projectInfo) {
|
|
169
|
-
this.config.environment = this.client._projectInfo.default_environment;
|
|
170
|
-
logger.info('Using auto-detected default environment', {
|
|
171
|
-
environment: this.config.environment,
|
|
172
|
-
source: 'api_key_project_config'
|
|
173
|
-
});
|
|
174
|
-
}
|
|
175
|
-
|
|
176
164
|
const browser = details.browser?.displayName || details.browser?.name || 'Cypress';
|
|
177
165
|
const platform = details.system?.platform || process.platform;
|
|
178
166
|
const os = this.detectOS(platform);
|
|
@@ -137,18 +137,6 @@ class JestReporter {
|
|
|
137
137
|
*/
|
|
138
138
|
async createRun() {
|
|
139
139
|
try {
|
|
140
|
-
// Auto-configure project from API key if needed
|
|
141
|
-
await this.client.ensureProjectConfig();
|
|
142
|
-
|
|
143
|
-
// Use auto-detected environment if not explicitly provided
|
|
144
|
-
if (!this.config.environment && this.client._projectInfo) {
|
|
145
|
-
this.config.environment = this.client._projectInfo.default_environment;
|
|
146
|
-
logger.info('Using auto-detected default environment', {
|
|
147
|
-
environment: this.config.environment,
|
|
148
|
-
source: 'api_key_project_config'
|
|
149
|
-
});
|
|
150
|
-
}
|
|
151
|
-
|
|
152
140
|
const os = this.detectOS();
|
|
153
141
|
|
|
154
142
|
const runOptions = {
|