@sasjs/cli 6.2.7 → 6.4.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.
@@ -51,6 +51,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
51
51
  };
52
52
  Object.defineProperty(exports, "__esModule", { value: true });
53
53
  var __1 = require("../");
54
+ var compileTestFile_1 = require("../compileTestFile");
54
55
  var utils_1 = require("@sasjs/utils");
55
56
  var test_1 = require("../../../../utils/test");
56
57
  var path_1 = __importDefault(require("path"));
@@ -391,6 +392,163 @@ describe('compileTestFile', function () {
391
392
  }
392
393
  });
393
394
  }); });
395
+ it('should write the coverage report to the build folder', function () { return __awaiter(void 0, void 0, void 0, function () {
396
+ var report, _a, _b;
397
+ return __generator(this, function (_c) {
398
+ switch (_c.label) {
399
+ case 0: return [4 /*yield*/, (0, compile_1.compile)(target)];
400
+ case 1:
401
+ _c.sent();
402
+ _b = (_a = JSON).parse;
403
+ return [4 /*yield*/, (0, utils_1.readFile)(path_1.default.join(buildPath, compileTestFile_1.coverageFileName))];
404
+ case 2:
405
+ report = _b.apply(_a, [_c.sent()]);
406
+ expect(report.summary).toEqual({
407
+ services: { covered: 0, total: 4, percentage: 0 },
408
+ jobs: { covered: 0, total: 0, percentage: 0 },
409
+ macros: { covered: 0, total: 0, percentage: 0 },
410
+ overall: { covered: 0, total: 4, percentage: 0 }
411
+ });
412
+ // the same rows the console table lists, standalone tests included
413
+ expect(report.files[['services', 'common', 'appinit.sas'].join('/')]).toEqual({ Type: 'service', Coverage: 'not covered' });
414
+ expect(report.files[['tests', 'jobs', 'jobs', 'testJob.test.sas'].join('/')]).toEqual({ Type: 'test', Coverage: 'standalone' });
415
+ return [2 /*return*/];
416
+ }
417
+ });
418
+ }); });
419
+ it('should key the report by project-relative paths, never build machine paths', function () { return __awaiter(void 0, void 0, void 0, function () {
420
+ var macroPath, tableSpy, report, _a, _b, tableRows;
421
+ return __generator(this, function (_c) {
422
+ switch (_c.label) {
423
+ case 0:
424
+ macroPath = path_1.default.join(sasjsPath, 'macros', 'uncoveredmacro.sas');
425
+ tableSpy = jest
426
+ .spyOn(process.logger, 'table')
427
+ .mockImplementation(function () { return ''; });
428
+ return [4 /*yield*/, (0, utils_1.createFile)(macroPath, '%macro uncoveredmacro(); %mend uncoveredmacro;')];
429
+ case 1:
430
+ _c.sent();
431
+ _c.label = 2;
432
+ case 2:
433
+ _c.trys.push([2, , 5, 7]);
434
+ return [4 /*yield*/, (0, compile_1.compile)(target)];
435
+ case 3:
436
+ _c.sent();
437
+ _b = (_a = JSON).parse;
438
+ return [4 /*yield*/, (0, utils_1.readFile)(path_1.default.join(buildPath, compileTestFile_1.coverageFileName))];
439
+ case 4:
440
+ report = _b.apply(_a, [_c.sent()]);
441
+ // macros are keyed like services and jobs, in the report and in the
442
+ // console table alike - a build machine path in either would make the
443
+ // same project produce different output elsewhere.
444
+ expect(report.files['macros/uncoveredmacro.sas']).toEqual({
445
+ Type: 'macro',
446
+ Coverage: 'not covered'
447
+ });
448
+ expect(Object.keys(report.files).filter(function (key) { return path_1.default.isAbsolute(key); })).toEqual([]);
449
+ expect(report.summary.macros).toEqual({
450
+ covered: 0,
451
+ total: 1,
452
+ percentage: 0
453
+ });
454
+ tableRows = tableSpy.mock.calls[0][0];
455
+ expect(tableRows).toContainEqual([
456
+ 'macros/uncoveredmacro.sas',
457
+ 'macro',
458
+ 'not covered'
459
+ ]);
460
+ expect(tableRows.flat().filter(function (cell) { return path_1.default.isAbsolute(cell); })).toEqual([]);
461
+ return [3 /*break*/, 7];
462
+ case 5: return [4 /*yield*/, (0, utils_1.deleteFile)(macroPath)];
463
+ case 6:
464
+ _c.sent();
465
+ return [7 /*endfinally*/];
466
+ case 7: return [2 /*return*/];
467
+ }
468
+ });
469
+ }); });
470
+ it('should mark covered files in the report, not just the gaps', function () { return __awaiter(void 0, void 0, void 0, function () {
471
+ var macroPath, macroTestPath, tableSpy, report, _a, _b, tableRows;
472
+ return __generator(this, function (_c) {
473
+ switch (_c.label) {
474
+ case 0:
475
+ macroPath = path_1.default.join(sasjsPath, 'macros', 'coveredmacro.sas');
476
+ macroTestPath = path_1.default.join(sasjsPath, 'macros', 'coveredmacro.test.sas');
477
+ tableSpy = jest
478
+ .spyOn(process.logger, 'table')
479
+ .mockImplementation(function () { return ''; });
480
+ return [4 /*yield*/, (0, utils_1.createFile)(macroPath, '%macro coveredmacro(); %mend coveredmacro;')];
481
+ case 1:
482
+ _c.sent();
483
+ return [4 /*yield*/, (0, utils_1.createFile)(macroTestPath, '%put testing coveredmacro;')];
484
+ case 2:
485
+ _c.sent();
486
+ _c.label = 3;
487
+ case 3:
488
+ _c.trys.push([3, , 6, 9]);
489
+ return [4 /*yield*/, (0, compile_1.compile)(target)];
490
+ case 4:
491
+ _c.sent();
492
+ _b = (_a = JSON).parse;
493
+ return [4 /*yield*/, (0, utils_1.readFile)(path_1.default.join(buildPath, compileTestFile_1.coverageFileName))];
494
+ case 5:
495
+ report = _b.apply(_a, [_c.sent()]);
496
+ expect(report.files['macros/coveredmacro.sas']).toEqual({
497
+ Type: 'macro',
498
+ Coverage: 'covered'
499
+ });
500
+ expect(report.summary.macros).toEqual({
501
+ covered: 1,
502
+ total: 1,
503
+ percentage: 100
504
+ });
505
+ tableRows = tableSpy.mock.calls[0][0];
506
+ expect(tableRows).toContainEqual([
507
+ 'macros/coveredmacro.sas',
508
+ 'macro',
509
+ 'covered'
510
+ ]);
511
+ return [3 /*break*/, 9];
512
+ case 6: return [4 /*yield*/, (0, utils_1.deleteFile)(macroPath)];
513
+ case 7:
514
+ _c.sent();
515
+ return [4 /*yield*/, (0, utils_1.deleteFile)(macroTestPath)];
516
+ case 8:
517
+ _c.sent();
518
+ return [7 /*endfinally*/];
519
+ case 9: return [2 /*return*/];
520
+ }
521
+ });
522
+ }); });
523
+ it('should not write a coverage report when no tests were compiled', function () { return __awaiter(void 0, void 0, void 0, function () {
524
+ var coveragePath, testFlow;
525
+ return __generator(this, function (_a) {
526
+ switch (_a.label) {
527
+ case 0: return [4 /*yield*/, (0, compile_1.compile)(target)];
528
+ case 1:
529
+ _a.sent();
530
+ coveragePath = path_1.default.join(buildPath, compileTestFile_1.coverageFileName);
531
+ // remove the report the compile wrote, so the assertion below is about
532
+ // the no-tests path and not about a file left over from it
533
+ return [4 /*yield*/, (0, utils_1.deleteFile)(coveragePath)];
534
+ case 2:
535
+ // remove the report the compile wrote, so the assertion below is about
536
+ // the no-tests path and not about a file left over from it
537
+ _a.sent();
538
+ return [4 /*yield*/, (0, utils_1.deleteFolder)(path_1.default.join(buildPath, 'tests'))];
539
+ case 3:
540
+ _a.sent();
541
+ return [4 /*yield*/, (0, __1.compileTestFlow)(target)];
542
+ case 4:
543
+ testFlow = _a.sent();
544
+ expect(testFlow).toBeUndefined();
545
+ return [4 /*yield*/, expect((0, utils_1.fileExists)(coveragePath)).resolves.toEqual(false)];
546
+ case 5:
547
+ _a.sent();
548
+ return [2 /*return*/];
549
+ }
550
+ });
551
+ }); });
394
552
  it('should log a warning is testFolders was used in root or target configuration', function () { return __awaiter(void 0, void 0, void 0, function () {
395
553
  var testTarget, testConfig, expectedWarn;
396
554
  return __generator(this, function (_a) {
@@ -97,6 +97,7 @@ function printHelpText() {
97
97
  "compiles the services specified in the ".concat(chalk_1.default.cyanBright('serviceFolders'), " property of the 'serviceConfig' in your target and common configuration."),
98
98
  "A single file is compiled by naming its type as a sub command - ".concat(chalk_1.default.cyanBright('job'), ", ").concat(chalk_1.default.cyanBright('service'), ", ").concat(chalk_1.default.cyanBright('test'), " or ").concat(chalk_1.default.cyanBright('identify'), ", which works the type out from the file itself - together with ").concat(chalk_1.default.cyanBright('--source'), " (or ").concat(chalk_1.default.cyanBright('-s'), ")."),
99
99
  "The compiled file is written to the ".concat(chalk_1.default.cyanBright('jobs'), " or ").concat(chalk_1.default.cyanBright('services'), " folder of sasjsbuild for its type, or below ").concat(chalk_1.default.cyanBright('--output'), " (or ").concat(chalk_1.default.cyanBright('-o'), ") when given."),
100
+ "The test coverage report is written to ".concat(chalk_1.default.cyanBright('coverage.json'), " in the build folder alongside ").concat(chalk_1.default.cyanBright('testFlow.json'), "."),
100
101
  "command example: sasjs compile identify --source sasjs/jobs/daily/load.sas --target myTarget"
101
102
  ]
102
103
  },
@@ -2,6 +2,6 @@ export { CommandBase } from './command/commandBase';
2
2
  export { File, FilePath } from './file';
3
3
  export { Folder } from './folder';
4
4
  export { TargetScope } from './targetScope';
5
- export { TestFlow, Coverage, CoverageType, CoverageState, TestResults, TestResultStatus, TestDescription, TestResult, TestResultDescription, TestResultCsv } from './testing';
5
+ export { TestFlow, Coverage, CoverageReport, CoverageSummary, CoverageType, CoverageState, TestResults, TestResultStatus, TestDescription, TestResult, TestResultDescription, TestResultCsv } from './testing';
6
6
  export { Flow, FlowWave, FlowWaveJob } from './flow';
7
7
  export * from './log';
@@ -12,6 +12,7 @@ export declare enum CoverageType {
12
12
  }
13
13
  export declare enum CoverageState {
14
14
  notCovered = "not covered",
15
+ covered = "covered",
15
16
  standalone = "standalone"
16
17
  }
17
18
  export interface Coverage {
@@ -20,6 +21,38 @@ export interface Coverage {
20
21
  Coverage: CoverageState;
21
22
  };
22
23
  }
24
+ /**
25
+ * Totals for one group of files (services, jobs, macros or all of them).
26
+ * `percentage` is rounded to a whole number, matching the figure printed in
27
+ * the console summary.
28
+ */
29
+ export interface CoverageSummary {
30
+ covered: number;
31
+ total: number;
32
+ percentage: number;
33
+ }
34
+ /**
35
+ * The content of the coverage report `sasjs compile` writes to
36
+ * `coverage.json` in the build folder.
37
+ *
38
+ * `summary` carries the same totals as the console output. `files` is a
39
+ * complete per-file map - every service, job and macro the compile found,
40
+ * each marked `covered` or `not covered`, plus standalone tests - keyed by
41
+ * project-relative path (`services/common/appinit.sas`,
42
+ * `macros/mymacro.sas`), never by a path from the machine that compiled it.
43
+ *
44
+ * The console table renders this same map, so the rows a reader sees and the
45
+ * keys a consumer reads are the same, covered rows included.
46
+ */
47
+ export interface CoverageReport {
48
+ summary: {
49
+ services: CoverageSummary;
50
+ jobs: CoverageSummary;
51
+ macros: CoverageSummary;
52
+ overall: CoverageSummary;
53
+ };
54
+ files: Coverage;
55
+ }
23
56
  export interface TestResultDescription {
24
57
  TEST_DESCRIPTION: string;
25
58
  TEST_RESULT: TestResultStatus.pass | TestResultStatus.fail;
@@ -11,6 +11,7 @@ var CoverageType;
11
11
  var CoverageState;
12
12
  (function (CoverageState) {
13
13
  CoverageState["notCovered"] = "not covered";
14
+ CoverageState["covered"] = "covered";
14
15
  CoverageState["standalone"] = "standalone";
15
16
  })(CoverageState || (exports.CoverageState = CoverageState = {}));
16
17
  var TestResultStatus;
@@ -59,3 +59,33 @@ export declare function getNewAccessToken(sasjsInstance: SASjs, clientId: string
59
59
  access_token: string;
60
60
  refresh_token: string;
61
61
  }>;
62
+ /**
63
+ * Mints a token pair from the local login of a SASjs Server target.
64
+ *
65
+ * SASjs Server has no secret-less OAuth client and no password grant: the
66
+ * tokens it issues come from its own sign-in flow, the one its web app uses.
67
+ * Reproducing that flow here is what makes `sasjs auth login` usable on a SASjs
68
+ * target from CI, where there is no browser to visit /SASjsLogon and no
69
+ * administrator-registered client secret to fall back on:
70
+ *
71
+ * GET / sets the XSRF-TOKEN cookie
72
+ * POST /SASLogon/login creates the session
73
+ * POST /SASLogon/authorize returns a single-use authorization code
74
+ * POST /SASjsApi/auth/token exchanges it for an access/refresh pair
75
+ *
76
+ * @param {Target} target - the SASJS target to authenticate against.
77
+ * @param {string} user - the SASjs Server username.
78
+ * @param {string} pass - the SASjs Server password.
79
+ * @param {string} [clientId] - the registered client to authorize, usually from
80
+ * the SASJS_CLIENT environment variable. When absent, the target's configured
81
+ * client is used, and failing that the first registered client if the account
82
+ * is an administrator.
83
+ * @returns the token pair, the client that was authorized, and the display name
84
+ * the server reports for the user.
85
+ */
86
+ export declare function getTokensWithSasjsLogin(target: Target, user: string, pass: string, clientId?: string): Promise<{
87
+ access_token: string;
88
+ refresh_token: string;
89
+ client: string;
90
+ displayName?: string;
91
+ }>;
@@ -1,4 +1,37 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
2
35
  var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
36
  function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
37
  return new (P || (P = Promise))(function (resolve, reject) {
@@ -35,6 +68,34 @@ var __generator = (this && this.__generator) || function (thisArg, body) {
35
68
  if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
36
69
  }
37
70
  };
71
+ var __read = (this && this.__read) || function (o, n) {
72
+ var m = typeof Symbol === "function" && o[Symbol.iterator];
73
+ if (!m) return o;
74
+ var i = m.call(o), r, ar = [], e;
75
+ try {
76
+ while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
77
+ }
78
+ catch (error) { e = { error: error }; }
79
+ finally {
80
+ try {
81
+ if (r && !r.done && (m = i["return"])) m.call(i);
82
+ }
83
+ finally { if (e) throw e.error; }
84
+ }
85
+ return ar;
86
+ };
87
+ var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
88
+ if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
89
+ if (ar || !(i in from)) {
90
+ if (!ar) ar = Array.prototype.slice.call(from, 0, i);
91
+ ar[i] = from[i];
92
+ }
93
+ }
94
+ return to.concat(ar || Array.prototype.slice.call(from));
95
+ };
96
+ var __importDefault = (this && this.__importDefault) || function (mod) {
97
+ return (mod && mod.__esModule) ? mod : { "default": mod };
98
+ };
38
99
  Object.defineProperty(exports, "__esModule", { value: true });
39
100
  exports.SAS_CLI_CLIENT_ID = exports.getAuthUrl = void 0;
40
101
  exports.getAuthCode = getAuthCode;
@@ -42,6 +103,9 @@ exports.refreshTokens = refreshTokens;
42
103
  exports.getTokensWithPasswordGrant = getTokensWithPasswordGrant;
43
104
  exports.fetchLoggedInUser = fetchLoggedInUser;
44
105
  exports.getNewAccessToken = getNewAccessToken;
106
+ exports.getTokensWithSasjsLogin = getTokensWithSasjsLogin;
107
+ var axios_1 = __importDefault(require("axios"));
108
+ var https = __importStar(require("https"));
45
109
  var utils_1 = require("@sasjs/utils");
46
110
  var node_1 = require("@sasjs/adapter/node");
47
111
  /**
@@ -217,3 +281,169 @@ function getNewAccessToken(sasjsInstance, clientId, clientSecret, target) {
217
281
  });
218
282
  });
219
283
  }
284
+ /**
285
+ * Mints a token pair from the local login of a SASjs Server target.
286
+ *
287
+ * SASjs Server has no secret-less OAuth client and no password grant: the
288
+ * tokens it issues come from its own sign-in flow, the one its web app uses.
289
+ * Reproducing that flow here is what makes `sasjs auth login` usable on a SASjs
290
+ * target from CI, where there is no browser to visit /SASjsLogon and no
291
+ * administrator-registered client secret to fall back on:
292
+ *
293
+ * GET / sets the XSRF-TOKEN cookie
294
+ * POST /SASLogon/login creates the session
295
+ * POST /SASLogon/authorize returns a single-use authorization code
296
+ * POST /SASjsApi/auth/token exchanges it for an access/refresh pair
297
+ *
298
+ * @param {Target} target - the SASJS target to authenticate against.
299
+ * @param {string} user - the SASjs Server username.
300
+ * @param {string} pass - the SASjs Server password.
301
+ * @param {string} [clientId] - the registered client to authorize, usually from
302
+ * the SASJS_CLIENT environment variable. When absent, the target's configured
303
+ * client is used, and failing that the first registered client if the account
304
+ * is an administrator.
305
+ * @returns the token pair, the client that was authorized, and the display name
306
+ * the server reports for the user.
307
+ */
308
+ function getTokensWithSasjsLogin(target, user, pass, clientId) {
309
+ return __awaiter(this, void 0, void 0, function () {
310
+ var baseUrl, cookies, safeDecode, collectCookies, instance, call, fail, login, client, clients, authorized, code, tokens;
311
+ var _this = this;
312
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j;
313
+ return __generator(this, function (_k) {
314
+ switch (_k.label) {
315
+ case 0:
316
+ baseUrl = target.serverUrl.replace(/\/+$/, '');
317
+ cookies = new Map();
318
+ safeDecode = function (value) {
319
+ try {
320
+ return decodeURIComponent(value);
321
+ }
322
+ catch (_a) {
323
+ return value;
324
+ }
325
+ };
326
+ collectCookies = function (setCookie) {
327
+ var entries = Array.isArray(setCookie)
328
+ ? setCookie
329
+ : setCookie
330
+ ? [setCookie]
331
+ : [];
332
+ entries.forEach(function (entry) {
333
+ var pair = entry.split(';')[0];
334
+ var index = pair.indexOf('=');
335
+ if (index > 0) {
336
+ cookies.set(pair.slice(0, index).trim(), pair.slice(index + 1).trim());
337
+ }
338
+ });
339
+ };
340
+ instance = axios_1.default.create({
341
+ baseURL: baseUrl,
342
+ validateStatus: function () { return true; },
343
+ // The login request carries the password in a body, so a redirect must not
344
+ // replay it to wherever 'Location' points. A redirect is a configuration
345
+ // problem (proxy, SSO front end) - report it instead of following it.
346
+ maxRedirects: 0,
347
+ httpsAgent: target.httpsAgentOptions
348
+ ? new https.Agent(target.httpsAgentOptions)
349
+ : undefined,
350
+ headers: { Accept: 'application/json' }
351
+ });
352
+ call = function (method, path, data) { return __awaiter(_this, void 0, void 0, function () {
353
+ var headers, xsrf, response;
354
+ return __generator(this, function (_a) {
355
+ switch (_a.label) {
356
+ case 0:
357
+ headers = {};
358
+ if (cookies.size) {
359
+ headers.Cookie = __spreadArray([], __read(cookies), false).map(function (_a) {
360
+ var _b = __read(_a, 2), name = _b[0], value = _b[1];
361
+ return "".concat(name, "=").concat(value);
362
+ })
363
+ .join('; ');
364
+ }
365
+ xsrf = cookies.get('XSRF-TOKEN');
366
+ if (xsrf)
367
+ headers['X-XSRF-TOKEN'] = safeDecode(xsrf);
368
+ return [4 /*yield*/, instance.request({
369
+ method: method,
370
+ url: path,
371
+ data: data,
372
+ headers: headers
373
+ })];
374
+ case 1:
375
+ response = _a.sent();
376
+ if (response.status >= 300 && response.status < 400) {
377
+ throw new Error("".concat(baseUrl).concat(path, " answered with a ").concat(response.status, " redirect to ") +
378
+ "'".concat(response.headers.location || 'unknown', "'. It is not followed: ") +
379
+ "the sign-in request carries the password. Point the target at the " +
380
+ "final URL.");
381
+ }
382
+ collectCookies(response.headers['set-cookie']);
383
+ return [2 /*return*/, response];
384
+ }
385
+ });
386
+ }); };
387
+ fail = function (message) {
388
+ return new Error("".concat(message, "\nCheck that '").concat(user, "' can sign in to ").concat(target.serverUrl, ", and that ") +
389
+ "the client id is set (SASJS_CLIENT) if the account is not an administrator.");
390
+ };
391
+ // GET / serves the single page app but sets the XSRF cookie the login needs.
392
+ return [4 /*yield*/, call('get', '/')];
393
+ case 1:
394
+ // GET / serves the single page app but sets the XSRF cookie the login needs.
395
+ _k.sent();
396
+ return [4 /*yield*/, call('post', '/SASLogon/login', {
397
+ username: user,
398
+ password: pass
399
+ })];
400
+ case 2:
401
+ login = _k.sent();
402
+ if (login.status !== 200 || !((_a = login.data) === null || _a === void 0 ? void 0 : _a.loggedIn)) {
403
+ throw fail("Login failed for user '".concat(user, "' on ").concat(target.serverUrl, " (HTTP ").concat(login.status, ")."));
404
+ }
405
+ client = clientId || ((_b = target.authConfig) === null || _b === void 0 ? void 0 : _b.client) || '';
406
+ if (!!client) return [3 /*break*/, 4];
407
+ return [4 /*yield*/, call('get', '/SASjsApi/client')];
408
+ case 3:
409
+ clients = _k.sent();
410
+ if (Array.isArray(clients.data) && clients.data.length) {
411
+ client = clients.data[0].clientId;
412
+ // Say which one was picked: the listing order is the server's, and a
413
+ // silent choice here is impossible to debug from the tokens alone.
414
+ (_c = process.logger) === null || _c === void 0 ? void 0 : _c.info("Using the first registered client ('".concat(client, "'). Set SASJS_CLIENT ") +
415
+ "to authorize a specific client.");
416
+ }
417
+ _k.label = 4;
418
+ case 4:
419
+ if (!client) {
420
+ throw fail('No client id is available to authorize.');
421
+ }
422
+ return [4 /*yield*/, call('post', '/SASLogon/authorize', {
423
+ clientId: client
424
+ })];
425
+ case 5:
426
+ authorized = _k.sent();
427
+ code = (_d = authorized.data) === null || _d === void 0 ? void 0 : _d.code;
428
+ if (!code) {
429
+ throw fail("Authorization failed for client '".concat(client, "' (HTTP ").concat(authorized.status, ")."));
430
+ }
431
+ return [4 /*yield*/, call('post', '/SASjsApi/auth/token', {
432
+ clientId: client,
433
+ code: code
434
+ })];
435
+ case 6:
436
+ tokens = _k.sent();
437
+ if (!((_e = tokens.data) === null || _e === void 0 ? void 0 : _e.accessToken)) {
438
+ throw fail("The token endpoint returned no access token (HTTP ".concat(tokens.status, ")."));
439
+ }
440
+ return [2 /*return*/, {
441
+ access_token: tokens.data.accessToken,
442
+ refresh_token: tokens.data.refreshToken || '',
443
+ client: client,
444
+ displayName: ((_g = (_f = login.data) === null || _f === void 0 ? void 0 : _f.user) === null || _g === void 0 ? void 0 : _g.displayName) || ((_j = (_h = login.data) === null || _h === void 0 ? void 0 : _h.user) === null || _j === void 0 ? void 0 : _j.username)
445
+ }];
446
+ }
447
+ });
448
+ });
449
+ }
@@ -968,7 +968,7 @@ var saveTokens = function (targetName, access_token, refresh_token, client, secr
968
968
  if (!isLocalTarget) return [3 /*break*/, 3];
969
969
  VERBOSE = process.env.VERBOSE;
970
970
  clientSecretContent = client
971
- ? "CLIENT=".concat(client, "\nSECRET=").concat(secret || '', "\n")
971
+ ? "CLIENT=".concat(client, "\n").concat(secret ? "SECRET=".concat(secret, "\n") : '')
972
972
  : '';
973
973
  envFileContent = "".concat(clientSecretContent, "ACCESS_TOKEN=").concat(access_token, "\nREFRESH_TOKEN=").concat(refresh_token, "\n").concat(VERBOSE ? 'VERBOSE=' + VERBOSE + '\n' : '');
974
974
  envFilePath = path_1.default.join(process.projectDir, ".env.".concat(targetName));
@@ -986,7 +986,7 @@ var saveTokens = function (targetName, access_token, refresh_token, client, secr
986
986
  }
987
987
  targetJson = __assign({}, target);
988
988
  targetJson.authConfig = __assign(__assign(__assign({}, (targetJson.authConfig || {})), (client
989
- ? { client: client, secret: secret || '' }
989
+ ? { client: client, secret: secret || undefined }
990
990
  : { client: undefined, secret: undefined })), { access_token: access_token, refresh_token: refresh_token });
991
991
  return [4 /*yield*/, saveToGlobalConfig(new utils_1.Target(targetJson), globalConfig.defaultTarget === targetName)];
992
992
  case 5:
@@ -0,0 +1 @@
1
+ export {};