@appliqation/automation-sdk 2.3.2 → 2.5.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.
@@ -4,6 +4,7 @@ const RunMatrixService = require('./services/RunMatrixService');
4
4
  const ResultService = require('./services/ResultService');
5
5
  const TaggingService = require('./services/TaggingService');
6
6
  const OrphanTestService = require('./services/OrphanTestService');
7
+ const ProjectInfoService = require('./services/ProjectInfoService');
7
8
  const UuidValidator = require('./utils/UuidValidator');
8
9
  const PayloadBuilder = require('./utils/PayloadBuilder');
9
10
  const logger = require('./utils/logger');
@@ -13,102 +14,82 @@ const { DEFAULT_APPLIQATION_BASE_URL } = require('./constants');
13
14
  * Main Appliqation Automation SDK Client
14
15
  *
15
16
  * @example
16
- * // Initialize with API key (recommended for automation)
17
+ * // Minimal config project auto-discovered from API key
17
18
  * const client = new AppliqationClient({
18
- * baseUrl: 'https://your-instance.appliqation.com',
19
- * apiKey: 'appq_live_xxxxxxxxxxxxx',
20
- * projectKey: 'your-project-key'
21
- * });
22
- *
23
- * // Create run matrix
24
- * const run = await client.createRun({
25
- * scenarioId: 123,
26
- * environment: 'Production',
27
- * browsers: ['Chrome', 'Firefox'],
28
- * device: 'Desktop',
29
- * os: 'Windows 11'
30
- * });
31
- *
32
- * // Submit test results
33
- * await client.submitResult(run.runId, {
34
- * uuid: '124-d1f9559c-b978-43cc-9c76-fd539c717cb4',
35
- * status: 'passed',
36
- * browser: 'Chrome'
19
+ * apiKey: process.env.APPLIQATION_API_KEY
37
20
  * });
21
+ * await client.initialize(); // auto-discovers project
38
22
  */
39
23
  class AppliqationClient {
24
+
40
25
  /**
41
- * Create Appliqation client
42
- * @param {Object} config - Client configuration
43
- * @param {string} config.baseUrl - Appliqation instance URL
44
- * @param {string} config.apiKey - API key for authentication (recommended)
45
- * @param {string} config.projectKey - Project key (base64 encoded or plain)
46
- * @param {string} [config.username] - Username for CSRF auth (legacy)
47
- * @param {string} [config.password] - Password for CSRF auth (legacy)
48
- * @param {string} [config.title] - Custom run title (or use APPLIQATION_RUN_TITLE env var)
49
- * @param {Object} [config.options] - Additional options
50
- * @param {number} [config.options.timeout=30000] - Request timeout in ms
51
- * @param {number} [config.options.retries=3] - Number of retry attempts
52
- * @param {boolean} [config.options.logOrphans=true] - Log orphan tests to backend
53
- * @param {string} [config.options.logLevel='info'] - Logging level
26
+ * Resolve configuration from multiple sources.
27
+ * Priority: explicit config > env vars > defaults
28
+ *
29
+ * @param {Object} config - User-provided configuration
30
+ * @returns {Object} Resolved configuration
54
31
  */
55
- constructor(config) {
56
- // Apply SDK default for baseUrl when not provided
57
- const resolvedBaseUrl = (config?.baseUrl || process.env.APPLIQATION_BASE_URL || DEFAULT_APPLIQATION_BASE_URL || '').replace(/\/$/, '');
58
- const normalizedConfig = {
59
- ...(config || {}),
60
- baseUrl: resolvedBaseUrl
32
+ static resolveConfig(config = {}) {
33
+ const apiKey = config.apiKey || process.env.APPLIQATION_API_KEY || null;
34
+ const baseUrl = (config.baseUrl || DEFAULT_APPLIQATION_BASE_URL || '').replace(/\/$/, '');
35
+
36
+ return {
37
+ ...config,
38
+ apiKey,
39
+ baseUrl,
40
+ projectKey: config.projectKey || process.env.APPLIQATION_PROJECT_KEY || null,
41
+ runTitle: config.runTitle || config.title || process.env.APPLIQATION_RUN_TITLE || null,
61
42
  };
43
+ }
62
44
 
63
- // Validate configuration
64
- this.validateConfig(normalizedConfig);
45
+ /**
46
+ * Create Appliqation client
47
+ * @param {Object} config - Client configuration (can be empty — uses resolveConfig)
48
+ */
49
+ constructor(config = {}) {
50
+ const resolved = AppliqationClient.resolveConfig(config);
51
+
52
+ // Validate — warn instead of throw
53
+ const validation = this.validateConfig(resolved);
54
+ if (!validation.valid) {
55
+ // Log warnings but don't throw
56
+ for (const warning of validation.warnings) {
57
+ logger.warn(warning);
58
+ }
59
+ }
65
60
 
66
61
  // Determine SSL enforcement based on environment
67
62
  const isProduction = this._isProductionEnvironment();
68
- let rejectUnauthorized = true; // Default to secure
69
-
70
- if (normalizedConfig.rejectUnauthorized !== undefined) {
71
- // User explicitly set rejectUnauthorized
72
- if (isProduction && normalizedConfig.rejectUnauthorized === false) {
73
- // WARNING: User is trying to disable SSL in production
74
- logger.warn('SSL certificate verification is disabled in production environment!', {
75
- environment: process.env.NODE_ENV,
76
- ci: process.env.CI,
77
- warning: 'This is a security risk. SSL verification should be enabled in production.'
78
- });
63
+ let rejectUnauthorized = true;
64
+
65
+ if (resolved.rejectUnauthorized !== undefined) {
66
+ if (isProduction && resolved.rejectUnauthorized === false) {
67
+ logger.warn('SSL certificate verification is disabled in production environment!');
79
68
  }
80
- rejectUnauthorized = normalizedConfig.rejectUnauthorized;
81
- } else if (isProduction) {
82
- // Production environment - force SSL verification
83
- rejectUnauthorized = true;
84
- logger.info('SSL certificate verification enforced for production environment');
85
- } else {
86
- // Development environment - default to secure but can be overridden
87
- rejectUnauthorized = true;
69
+ rejectUnauthorized = resolved.rejectUnauthorized;
88
70
  }
89
71
 
90
72
  // Store configuration
91
73
  this.config = {
92
- baseUrl: resolvedBaseUrl,
93
- apiKey: normalizedConfig.apiKey,
94
- projectKey: normalizedConfig.projectKey,
95
- username: normalizedConfig.username,
96
- password: normalizedConfig.password,
97
- runTitle: normalizedConfig.runTitle || normalizedConfig.title || process.env.APPLIQATION_RUN_TITLE || null,
98
- rejectUnauthorized: rejectUnauthorized,
74
+ baseUrl: resolved.baseUrl,
75
+ apiKey: resolved.apiKey,
76
+ projectKey: resolved.projectKey,
77
+ username: resolved.username,
78
+ password: resolved.password,
79
+ runTitle: resolved.runTitle,
80
+ rejectUnauthorized,
99
81
  options: {
100
- timeout: normalizedConfig.options?.timeout || 30000,
101
- retries: normalizedConfig.options?.retries || 3,
102
- logOrphans: normalizedConfig.options?.logOrphans !== false,
103
- logLevel: normalizedConfig.options?.logLevel || 'info',
104
- // Auto-tagging configuration
105
- autoTag: normalizedConfig.options?.autoTag !== false,
106
- autoTagName: normalizedConfig.options?.autoTagName ||
107
- normalizedConfig.autoTagName ||
82
+ timeout: resolved.options?.timeout || 30000,
83
+ retries: resolved.options?.retries || 3,
84
+ logOrphans: resolved.options?.logOrphans !== false,
85
+ logLevel: resolved.options?.logLevel || 'info',
86
+ autoTag: resolved.options?.autoTag !== false,
87
+ autoTagName: resolved.options?.autoTagName ||
88
+ resolved.autoTagName ||
108
89
  process.env.APPLIQATION_AUTO_TAG_NAME ||
109
90
  'Appq_automated',
110
- autoTagBatchSize: normalizedConfig.options?.autoTagBatchSize || 50,
111
- autoTagRetries: normalizedConfig.options?.autoTagRetries || 2
91
+ autoTagBatchSize: resolved.options?.autoTagBatchSize || 50,
92
+ autoTagRetries: resolved.options?.autoTagRetries || 2
112
93
  }
113
94
  };
114
95
 
@@ -124,17 +105,15 @@ class AppliqationClient {
124
105
  this.tagging = new TaggingService(this.http, this.config);
125
106
  this.results = new ResultService(this.http, this.tagging, this.config);
126
107
  this.orphans = new OrphanTestService(this.http);
108
+ this.projectInfo = new ProjectInfoService();
127
109
 
128
- // Log tagging configuration
129
- logger.debug('TaggingService initialized', {
130
- enabled: this.tagging.isEnabled(),
131
- tagName: this.tagging.tagName
132
- });
110
+ // Auto-discovery state
111
+ this.availableEnvironments = null;
112
+ this._initialized = false;
133
113
 
134
114
  // Track current run context
135
115
  this.currentRun = null;
136
116
 
137
- // Show baseUrl only in DEBUG mode
138
117
  const meta = {
139
118
  authMode: this.config.apiKey ? 'api_key' : 'csrf'
140
119
  };
@@ -142,29 +121,48 @@ class AppliqationClient {
142
121
  logger.debug('Client configuration', { baseUrl: this.config.baseUrl });
143
122
  }
144
123
 
124
+ /**
125
+ * Auto-discover project info from API key.
126
+ * Fills in missing projectKey and availableEnvironments.
127
+ * Never throws — logs warnings on failure.
128
+ *
129
+ * @returns {Promise<void>}
130
+ */
131
+ async initialize() {
132
+ if (this._initialized) return;
133
+ this._initialized = true;
134
+
135
+ if (!this.config.apiKey) return;
136
+
137
+ const info = await this.projectInfo.getProjectInfo(this.http);
138
+
139
+ if (info) {
140
+ // Auto-fill projectKey if not explicitly set
141
+ if (!this.config.projectKey && info.key) {
142
+ this.config.projectKey = info.key;
143
+ logger.info('Project auto-discovered', { projectKey: info.key, name: info.name });
144
+ }
145
+
146
+ // Store available environments for validation
147
+ if (info.environments && info.environments.length > 0) {
148
+ this.availableEnvironments = info.environments;
149
+ logger.debug('Available environments', { environments: info.environments });
150
+ }
151
+ } else if (!this.config.projectKey) {
152
+ console.warn('[Appliqation] Project auto-discovery failed. Tests will continue without reporting.');
153
+ }
154
+ }
155
+
145
156
  /**
146
157
  * Create a new test run matrix
147
- * @param {Object} options - Run configuration
148
- * @param {number} options.scenarioId - Scenario node ID
149
- * @param {number} options.testSetId - Test Set node ID (alternative to scenarioId)
150
- * @param {string} options.type - 'scenario' or 'testset'
151
- * @param {string} [options.environment='Local'] - Environment name
152
- * @param {string[]} [options.browsers=['Chrome']] - Array of browser names
153
- * @param {string} [options.device] - Device type (Desktop, Mobile, Tablet)
154
- * @param {string} [options.os] - Operating system
155
- * @param {string} [options.title] - Custom run title
156
- * @returns {Promise<Object>} { runId, token, timestamp, metadata }
157
158
  */
158
159
  async createRun(options) {
159
160
  try {
160
161
  logger.info('Creating run matrix...', options);
161
-
162
162
  const run = await this.runMatrix.create(options);
163
163
 
164
- // Store current run context
165
164
  this.currentRun = run;
166
165
 
167
- // Store run configuration in AuthManager for JWT refresh capability
168
166
  if (this.auth && this.config.apiKey) {
169
167
  this.auth.setRunConfig({
170
168
  projectKey: this.config.projectKey,
@@ -176,7 +174,6 @@ class AppliqationClient {
176
174
  os: options.os,
177
175
  title: options.title || this.config.runTitle
178
176
  });
179
- logger.debug('Run configuration stored in AuthManager for JWT refresh');
180
177
  }
181
178
 
182
179
  logger.info('Run matrix created successfully', {
@@ -196,90 +193,56 @@ class AppliqationClient {
196
193
 
197
194
  /**
198
195
  * Create multiple runs for device/OS matrix testing
199
- * @param {Array} configs - Array of run configurations
200
- * @returns {Promise<Object>} { runs, errors, summary }
201
196
  */
202
197
  async createMultipleRuns(configs) {
203
198
  try {
204
199
  logger.info(`Creating ${configs.length} run matrices...`);
205
-
206
200
  const result = await this.runMatrix.createMultiple(configs);
207
-
208
201
  logger.info('Multiple runs created', {
209
202
  total: result.summary.total,
210
203
  success: result.summary.success,
211
204
  failed: result.summary.failed
212
205
  });
213
-
214
206
  return result;
215
207
  } catch (error) {
216
- logger.error('Failed to create multiple runs', {
217
- error: error.message
218
- });
208
+ logger.error('Failed to create multiple runs', { error: error.message });
219
209
  throw error;
220
210
  }
221
211
  }
222
212
 
223
213
  /**
224
214
  * Submit a single test result
225
- * @param {string} runId - Run ID
226
- * @param {Object} result - Test result
227
- * @param {string} result.uuid - Test case UUID
228
- * @param {string} result.status - Test status (passed, failed, skipped)
229
- * @param {string} result.browser - Browser name
230
- * @param {string} [result.parent_uuid] - Parent test case UUID
231
- * @param {string} [result.comment] - Additional comments
232
- * @param {string} [result.environment] - Environment name
233
- * @returns {Promise<Object>} Response from server
234
215
  */
235
216
  async submitResult(runId, result) {
236
217
  try {
237
- // Validate UUID
238
218
  const validation = UuidValidator.validateAndExtract(result.uuid);
239
219
  if (!validation.valid) {
240
220
  throw new Error(`Invalid UUID: ${validation.error}`);
241
221
  }
242
-
243
- // Validate result fields
244
222
  const resultValidation = PayloadBuilder.validateResult(result);
245
223
  if (!resultValidation.valid) {
246
224
  throw new Error(`Invalid result: ${resultValidation.errors.join(', ')}`);
247
225
  }
248
226
 
249
227
  logger.debug('Submitting test result', {
250
- runId,
251
- uuid: result.uuid,
252
- status: result.status,
253
- browser: result.browser
228
+ runId, uuid: result.uuid, status: result.status, browser: result.browser
254
229
  });
255
230
 
256
- const response = await this.results.submitSingle(runId, result);
257
-
258
- return response;
231
+ return await this.results.submitSingle(runId, result);
259
232
  } catch (error) {
260
233
  logger.error('Failed to submit result', {
261
- error: error.message,
262
- runId,
263
- uuid: result.uuid
234
+ error: error.message, runId, uuid: result.uuid
264
235
  });
265
236
  throw error;
266
237
  }
267
238
  }
268
239
 
269
240
  /**
270
- * Submit batch of test results with smart grouping
271
- * @param {Array} results - Array of test results
272
- * @param {Object} [options] - Batch options
273
- * @param {number} [options.batchSize=50] - Results per batch
274
- * @param {Function} [options.onProgress] - Progress callback
275
- * @param {boolean} [options.retryFailures=true] - Retry failed submissions
276
- * @returns {Promise<Object>} { success, failed, total }
241
+ * Submit batch of test results
277
242
  */
278
243
  async submitBatch(results, options = {}) {
279
244
  try {
280
245
  logger.info(`Submitting batch of ${results.length} results...`);
281
-
282
- // Validate all results first
283
246
  const validResults = [];
284
247
  const invalidResults = [];
285
248
 
@@ -289,10 +252,7 @@ class AppliqationClient {
289
252
  validResults.push(result);
290
253
  } else {
291
254
  invalidResults.push({ result, error: validation.error });
292
- logger.warn('Invalid UUID in batch', {
293
- uuid: result.uuid,
294
- error: validation.error
295
- });
255
+ logger.warn('Invalid UUID in batch', { uuid: result.uuid, error: validation.error });
296
256
  }
297
257
  }
298
258
 
@@ -303,312 +263,171 @@ class AppliqationClient {
303
263
  const summary = await this.results.submitBatch(validResults, options);
304
264
 
305
265
  logger.info('Batch submission completed', {
306
- success: summary.success,
307
- failed: summary.failed,
308
- invalid: invalidResults.length,
309
- total: results.length
266
+ success: summary.success, failed: summary.failed,
267
+ invalid: invalidResults.length, total: results.length
310
268
  });
311
269
 
312
- return {
313
- ...summary,
314
- invalid: invalidResults.length,
315
- invalidResults
316
- };
270
+ return { ...summary, invalid: invalidResults.length, invalidResults };
317
271
  } catch (error) {
318
- logger.error('Failed to submit batch', {
319
- error: error.message,
320
- count: results.length
321
- });
272
+ logger.error('Failed to submit batch', { error: error.message, count: results.length });
322
273
  throw error;
323
274
  }
324
275
  }
325
276
 
326
277
  /**
327
- * Log orphan tests (tests without UUID mappings)
328
- * @param {string} runId - Run ID
329
- * @param {Array} orphanTests - Array of orphan test objects
330
- * @returns {Promise<Object>} Response from server
278
+ * Log orphan tests
331
279
  */
332
280
  async logOrphanTests(runId, orphanTests) {
333
281
  try {
334
282
  if (!this.config.options.logOrphans) {
335
- logger.debug('Orphan test logging is disabled');
336
283
  return { success: true, logged: 0 };
337
284
  }
338
-
339
285
  if (!orphanTests || orphanTests.length === 0) {
340
286
  return { success: true, logged: 0 };
341
287
  }
342
288
 
343
289
  logger.info(`Logging ${orphanTests.length} orphan tests...`);
344
-
345
290
  const response = await this.orphans.log(runId, orphanTests);
346
-
347
- logger.info(`Orphan tests logged successfully`, {
348
- runId,
349
- count: orphanTests.length
350
- });
351
-
291
+ logger.info(`Orphan tests logged successfully`, { runId, count: orphanTests.length });
352
292
  return response;
353
293
  } catch (error) {
354
294
  logger.error('Failed to log orphan tests', {
355
- error: error.message,
356
- runId,
357
- count: orphanTests.length
295
+ error: error.message, runId, count: orphanTests.length
358
296
  });
359
- // Don't throw - orphan logging should not break the test run
360
297
  return { success: false, error: error.message };
361
298
  }
362
299
  }
363
300
 
364
301
  /**
365
- * Add browser to existing run matrix dynamically
366
- * @param {string} runId - Run ID
367
- * @param {string} browser - Browser name
368
- * @param {number} nid - Optional scenario/testset node ID (defaults to 0)
369
- * @returns {Promise<Object>} Response from server
302
+ * Add browser to existing run matrix
370
303
  */
371
304
  async addBrowser(runId, browser, nid = 0) {
372
305
  try {
373
306
  logger.info('Adding browser to run matrix', { runId, browser, nid });
374
-
375
307
  const response = await this.runMatrix.addBrowser(runId, browser, nid);
376
-
377
308
  logger.info('Browser added successfully', { runId, browser });
378
-
379
309
  return response;
380
310
  } catch (error) {
381
- logger.error('Failed to add browser', {
382
- error: error.message,
383
- runId,
384
- browser
385
- });
311
+ logger.error('Failed to add browser', { error: error.message, runId, browser });
386
312
  throw error;
387
313
  }
388
314
  }
389
315
 
390
316
  /**
391
317
  * Get run matrix data
392
- * @param {string} runId - Run ID
393
- * @returns {Promise<Object>} Run matrix data
394
318
  */
395
319
  async getRun(runId) {
396
320
  try {
397
- const response = await this.runMatrix.get(runId);
398
- return response;
321
+ return await this.runMatrix.get(runId);
399
322
  } catch (error) {
400
- logger.error('Failed to get run matrix', {
401
- error: error.message,
402
- runId
403
- });
323
+ logger.error('Failed to get run matrix', { error: error.message, runId });
404
324
  throw error;
405
325
  }
406
326
  }
407
327
 
408
- /**
409
- * Get current run context (if set)
410
- * @returns {Object|null} Current run data or null
411
- */
412
328
  getCurrentRun() {
413
329
  return this.currentRun;
414
330
  }
415
331
 
416
- /**
417
- * Set current run context manually
418
- * @param {Object} run - Run data with runId
419
- */
420
332
  setCurrentRun(run) {
421
333
  this.currentRun = run;
422
334
  }
423
335
 
424
336
  /**
425
337
  * Delete a test run
426
- * @param {string} runId - Run ID to delete
427
- * @param {string} reason - Deletion reason (for audit logging)
428
- * @returns {Promise<Object>} Deletion result
429
338
  */
430
339
  async deleteRun(runId, reason = 'orphan_cleanup') {
431
340
  try {
432
341
  logger.info('Deleting run...', { runId, reason });
433
-
434
342
  const result = await this.runMatrix.delete(runId, reason);
435
-
436
- // Clear from current run if it matches
437
343
  if (this.currentRun && this.currentRun.runId === runId) {
438
344
  this.currentRun = null;
439
345
  }
440
-
441
346
  logger.info('Run deleted successfully', { runId });
442
-
443
347
  return result;
444
348
  } catch (error) {
445
- logger.error('Failed to delete run', {
446
- error: error.message,
447
- runId
448
- });
349
+ logger.error('Failed to delete run', { error: error.message, runId });
449
350
  throw error;
450
351
  }
451
352
  }
452
353
 
453
- /**
454
- * Validate UUID format
455
- * @param {string} uuid - UUID to validate
456
- * @returns {boolean} True if valid
457
- */
458
354
  validateUuid(uuid) {
459
355
  return UuidValidator.validate(uuid);
460
356
  }
461
357
 
462
- /**
463
- * Extract NID from UUID
464
- * @param {string} uuid - UUID string
465
- * @returns {number|null} NID or null if invalid
466
- */
467
358
  extractNid(uuid) {
468
359
  return UuidValidator.extractNid(uuid);
469
360
  }
470
361
 
471
- /**
472
- * Validate and extract UUID components
473
- * @param {string} uuid - UUID string
474
- * @returns {Object} { valid, nid, uuid, error }
475
- */
476
362
  parseUuid(uuid) {
477
363
  return UuidValidator.validateAndExtract(uuid);
478
364
  }
479
365
 
480
366
  /**
481
- * Validate client configuration
367
+ * Validate client configuration — returns warnings instead of throwing.
482
368
  * @private
369
+ * @returns {{ valid: boolean, warnings: string[] }}
483
370
  */
484
371
  validateConfig(config) {
372
+ const warnings = [];
373
+
485
374
  if (!config) {
486
- throw new Error(
487
- 'Configuration is required.\n' +
488
- 'Please provide configuration object:\n' +
489
- ' const client = new AppliqationClient({\n' +
490
- ' baseUrl: process.env.APPLIQATION_BASE_URL,\n' +
491
- ' apiKey: process.env.APPLIQATION_API_KEY,\n' +
492
- ' projectKey: process.env.APPLIQATION_PROJECT_KEY\n' +
493
- ' });'
494
- );
375
+ warnings.push('[Appliqation] No configuration provided');
376
+ return { valid: false, warnings };
495
377
  }
496
378
 
497
- if (!config.baseUrl) {
498
- throw new Error(
499
- 'baseUrl is required.\n' +
500
- 'Please set the APPLIQATION_BASE_URL environment variable or provide baseUrl in configuration:\n' +
501
- ' APPLIQATION_BASE_URL=https://your-instance.appliqation.com\n' +
502
- 'Or in code:\n' +
503
- ' { baseUrl: "https://your-instance.appliqation.com" }'
504
- );
379
+ if (!config.apiKey) {
380
+ warnings.push('[Appliqation] API key required. Set APPLIQATION_API_KEY in .env');
505
381
  }
506
382
 
507
383
  if (!config.projectKey) {
508
- throw new Error(
509
- 'projectKey is required.\n' +
510
- 'Please set the APPLIQATION_PROJECT_KEY environment variable or provide projectKey in configuration:\n' +
511
- ' APPLIQATION_PROJECT_KEY=your-project-key\n' +
512
- 'Or in code:\n' +
513
- ' { projectKey: "your-project-key" }\n' +
514
- '\n' +
515
- 'You can find your project key in the Appliqation dashboard under Project Settings.'
516
- );
384
+ // Not an error — will be auto-discovered
385
+ logger.debug('projectKey not set — will auto-discover from API key');
517
386
  }
518
387
 
519
- // Note: Both base64-encoded project keys AND numeric project IDs are now supported
520
- // Numeric IDs are a temporary workaround for duplicate project key issues
521
- // The backend will validate the project key format and access permissions
522
-
523
- // Must have either API key or username/password
524
- if (!config.apiKey && (!config.username || !config.password)) {
525
- throw new Error(
526
- 'Authentication credentials are required.\n' +
527
- 'Please set the APPLIQATION_API_KEY environment variable (recommended):\n' +
528
- ' APPLIQATION_API_KEY=appq_live_xxxxxxxxxxxx\n' +
529
- 'Or in code:\n' +
530
- ' { apiKey: "appq_live_xxxxxxxxxxxx" }\n' +
531
- '\n' +
532
- 'You can generate an API key in the Appliqation dashboard under Settings > API Keys.\n' +
533
- '\n' +
534
- 'Legacy CSRF authentication (username/password) is also supported but not recommended.'
535
- );
388
+ if (config.baseUrl) {
389
+ try {
390
+ new URL(config.baseUrl);
391
+ } catch (error) {
392
+ warnings.push(`[Appliqation] Invalid baseUrl format: "${config.baseUrl}"`);
393
+ }
536
394
  }
537
395
 
538
- // Validate URL format
539
- try {
540
- new URL(config.baseUrl);
541
- } catch (error) {
542
- throw new Error(
543
- `Invalid baseUrl format: "${config.baseUrl}"\n` +
544
- 'baseUrl must be a valid URL starting with http:// or https://\n' +
545
- 'Example: https://your-instance.appliqation.com'
546
- );
547
- }
396
+ return { valid: warnings.length === 0, warnings };
548
397
  }
549
398
 
550
399
  /**
551
- * Test connectivity to Appliqation instance
552
- * @returns {Promise<boolean>} True if connection successful
400
+ * Test connectivity
553
401
  */
554
402
  async testConnection() {
555
403
  try {
556
404
  logger.info('Testing connection to Appliqation...');
557
-
558
- // Try to authenticate
559
405
  await this.auth.authenticate();
560
-
561
406
  logger.info('Connection test successful');
562
407
  return true;
563
408
  } catch (error) {
564
- logger.error('Connection test failed', {
565
- error: error.message
566
- });
409
+ logger.error('Connection test failed', { error: error.message });
567
410
  throw new Error(`Connection test failed: ${error.message}`);
568
411
  }
569
412
  }
570
413
 
571
414
  /**
572
- * Determine if the current environment is production
573
415
  * @private
574
- * @returns {boolean} True if production environment
575
416
  */
576
417
  _isProductionEnvironment() {
577
- // Check for CI environments
578
- if (process.env.CI === 'true' || process.env.CI === '1') {
579
- return true;
580
- }
581
-
582
- // Check NODE_ENV
418
+ if (process.env.CI === 'true' || process.env.CI === '1') return true;
583
419
  const nodeEnv = (process.env.NODE_ENV || '').toLowerCase();
584
- if (nodeEnv === 'production' || nodeEnv === 'prod' || nodeEnv === 'staging') {
585
- return true;
586
- }
587
-
588
- // Check for common CI environment variables
420
+ if (nodeEnv === 'production' || nodeEnv === 'prod' || nodeEnv === 'staging') return true;
589
421
  const ciEnvVars = [
590
- 'GITHUB_ACTIONS',
591
- 'GITLAB_CI',
592
- 'CIRCLECI',
593
- 'TRAVIS',
594
- 'JENKINS_URL',
595
- 'TEAMCITY_VERSION',
596
- 'BUILDKITE'
422
+ 'GITHUB_ACTIONS', 'GITLAB_CI', 'CIRCLECI', 'TRAVIS',
423
+ 'JENKINS_URL', 'TEAMCITY_VERSION', 'BUILDKITE'
597
424
  ];
598
-
599
425
  for (const envVar of ciEnvVars) {
600
- if (process.env[envVar]) {
601
- return true;
602
- }
426
+ if (process.env[envVar]) return true;
603
427
  }
604
-
605
428
  return false;
606
429
  }
607
430
 
608
- /**
609
- * Get SDK version
610
- * @returns {string} Version string
611
- */
612
431
  getVersion() {
613
432
  return require('../package.json').version;
614
433
  }