@sasjs/cli 4.19.0 → 4.20.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.
@@ -3,6 +3,7 @@ import { TargetCommand } from '../../types/command/targetCommand';
3
3
  export declare class AuthCommand extends TargetCommand {
4
4
  constructor(args: string[]);
5
5
  get insecure(): boolean;
6
+ get passwordStdin(): boolean;
6
7
  execute(): Promise<ReturnCode.Success | ReturnCode.InternalError>;
7
8
  executeLogin(): Promise<ReturnCode.Success | ReturnCode.InternalError>;
8
9
  executeCred(): Promise<ReturnCode.Success | ReturnCode.InternalError>;
@@ -69,7 +69,10 @@ var usage = 'sasjs auth login --target <target-name> | sasjs auth [options]';
69
69
  var description = "Authenticates against the specified target.\n" +
70
70
  "With the 'login' subCommand, authentication is performed with a SAS username and password " +
71
71
  "(no OAuth client/secret required, SASVIYA targets only) and the resulting tokens are saved.\n" +
72
- "Without a subCommand, behaves like 'sasjs add cred' (client/secret based authentication).";
72
+ "Without a subCommand, behaves like 'sasjs add cred' (client/secret based authentication).\n" +
73
+ "\n" +
74
+ "Non-interactive use (CI/agents): set SAS_USERNAME and SAS_PASSWORD environment variables, " +
75
+ "or pipe the password and use --password-stdin.";
73
76
  var examples = [
74
77
  {
75
78
  command: 'sasjs auth login --target <target-name>',
@@ -79,6 +82,14 @@ var examples = [
79
82
  command: 'sasjs auth login -t <target-name>',
80
83
  description: ''
81
84
  },
85
+ {
86
+ command: 'SAS_USERNAME=usr SAS_PASSWORD=pass sasjs auth login -t <target-name>',
87
+ description: 'Non-interactive: read credentials from SAS_USERNAME/SAS_PASSWORD env vars (CI/agents).'
88
+ },
89
+ {
90
+ command: 'echo "$SAS_PASSWORD" | sasjs auth login --password-stdin -t <target-name>',
91
+ description: 'Non-interactive: read the password from stdin (avoids shell history).'
92
+ },
82
93
  {
83
94
  command: 'sasjs auth --target <target-name>',
84
95
  description: 'Legacy behaviour, equivalent to `sasjs add cred`.'
@@ -93,6 +104,14 @@ var AuthCommand = /** @class */ (function (_super) {
93
104
  alias: 'i',
94
105
  default: false,
95
106
  description: 'Allows the command to bypass the HTTPs requirement. Not recommended.'
107
+ },
108
+ passwordStdin: {
109
+ type: 'boolean',
110
+ default: false,
111
+ description: 'Read the password from stdin instead of prompting or reading the ' +
112
+ 'SAS_PASSWORD env var. Use this in CI/scripts to avoid leaking the ' +
113
+ 'password in shell history (e.g. ' +
114
+ '`echo "$SAS_PASSWORD" | sasjs auth login --password-stdin -t <target>`).'
96
115
  }
97
116
  };
98
117
  return _super.call(this, args, { parseOptions: parseOptions, usage: usage, description: description, examples: examples, syntax: syntax }) || this;
@@ -104,6 +123,13 @@ var AuthCommand = /** @class */ (function (_super) {
104
123
  enumerable: false,
105
124
  configurable: true
106
125
  });
126
+ Object.defineProperty(AuthCommand.prototype, "passwordStdin", {
127
+ get: function () {
128
+ return !!this.parsed.passwordStdin;
129
+ },
130
+ enumerable: false,
131
+ configurable: true
132
+ });
107
133
  AuthCommand.prototype.execute = function () {
108
134
  return __awaiter(this, void 0, void 0, function () {
109
135
  var _a;
@@ -136,7 +162,7 @@ var AuthCommand = /** @class */ (function (_super) {
136
162
  _b.label = 2;
137
163
  case 2:
138
164
  _b.trys.push([2, 4, , 5]);
139
- return [4 /*yield*/, (0, login_1.authLogin)(target, this.insecure)];
165
+ return [4 /*yield*/, (0, login_1.authLogin)(target, this.insecure, this.passwordStdin)];
140
166
  case 3:
141
167
  _b.sent();
142
168
  return [2 /*return*/, command_1.ReturnCode.Success];
@@ -6,11 +6,28 @@ import { Target } from '@sasjs/utils';
6
6
  * authenticated commands (`sasjs run`, `sasjs deploy`, etc.) on estates where
7
7
  * no administrator-registered OAuth client/secret is available.
8
8
  *
9
+ * Credentials are resolved in this order (highest precedence first):
10
+ *
11
+ * Username: SAS_USERNAME env var > interactive prompt (TTY only)
12
+ * Password: --password-stdin > SAS_PASSWORD env var > interactive prompt (TTY only)
13
+ *
14
+ * When `--password-stdin` is set, stdin is consumed for the password, so the
15
+ * username MUST come from the SAS_USERNAME env var (interactive prompting is
16
+ * not possible because stdin is already redirected).
17
+ *
18
+ * If no TTY is available and no env var / stdin flag supplies the credential,
19
+ * the function throws with a message pointing the caller at the env vars or
20
+ * `--password-stdin` flag — this makes `sasjs auth login` safe to call from
21
+ * CI pipelines and non-interactive agents.
22
+ *
9
23
  * The password is used only to mint the tokens and is never stored. When the
10
24
  * access token expires, re-run `sasjs auth login -t <target>`.
11
25
  * @param {Target} target - the target to authenticate against.
12
26
  * @param {boolean} insecure - when true, bypasses TLS certificate validation
13
27
  * (for self-signed cert Viya servers). Mirrors the `--insecure` flag on
14
28
  * `sasjs add cred`.
29
+ * @param {boolean} passwordStdin - when true, reads the password from stdin
30
+ * (like `docker login --password-stdin`) instead of prompting or reading
31
+ * the SAS_PASSWORD env var. The username must then come from SAS_USERNAME.
15
32
  */
16
- export declare const authLogin: (target: Target, insecure?: boolean) => Promise<void>;
33
+ export declare const authLogin: (target: Target, insecure?: boolean, passwordStdin?: boolean) => Promise<void>;
@@ -46,6 +46,13 @@ var __generator = (this && this.__generator) || function (thisArg, body) {
46
46
  if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
47
47
  }
48
48
  };
49
+ var __asyncValues = (this && this.__asyncValues) || function (o) {
50
+ if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
51
+ var m = o[Symbol.asyncIterator], i;
52
+ return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
53
+ function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
54
+ function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
55
+ };
49
56
  var __importDefault = (this && this.__importDefault) || function (mod) {
50
57
  return (mod && mod.__esModule) ? mod : { "default": mod };
51
58
  };
@@ -54,6 +61,61 @@ exports.authLogin = void 0;
54
61
  var prompts_1 = __importDefault(require("prompts"));
55
62
  var utils_1 = require("@sasjs/utils");
56
63
  var utils_2 = require("../../utils");
64
+ /**
65
+ * Reads a single line/value from stdin until EOF or newline, then trims
66
+ * trailing whitespace. Used by the `--password-stdin` flow so passwords can
67
+ * be piped without appearing in shell history or process argument lists.
68
+ */
69
+ var readPasswordFromStdin = function () { return __awaiter(void 0, void 0, void 0, function () {
70
+ var data, _a, _b, _c, chunk, e_1_1;
71
+ var _d, e_1, _e, _f;
72
+ return __generator(this, function (_g) {
73
+ switch (_g.label) {
74
+ case 0:
75
+ data = '';
76
+ _g.label = 1;
77
+ case 1:
78
+ _g.trys.push([1, 6, 7, 12]);
79
+ _a = true, _b = __asyncValues(process.stdin);
80
+ _g.label = 2;
81
+ case 2: return [4 /*yield*/, _b.next()];
82
+ case 3:
83
+ if (!(_c = _g.sent(), _d = _c.done, !_d)) return [3 /*break*/, 5];
84
+ _f = _c.value;
85
+ _a = false;
86
+ try {
87
+ chunk = _f;
88
+ data += chunk.toString();
89
+ }
90
+ finally {
91
+ _a = true;
92
+ }
93
+ _g.label = 4;
94
+ case 4: return [3 /*break*/, 2];
95
+ case 5: return [3 /*break*/, 12];
96
+ case 6:
97
+ e_1_1 = _g.sent();
98
+ e_1 = { error: e_1_1 };
99
+ return [3 /*break*/, 12];
100
+ case 7:
101
+ _g.trys.push([7, , 10, 11]);
102
+ if (!(!_a && !_d && (_e = _b.return))) return [3 /*break*/, 9];
103
+ return [4 /*yield*/, _e.call(_b)];
104
+ case 8:
105
+ _g.sent();
106
+ _g.label = 9;
107
+ case 9: return [3 /*break*/, 11];
108
+ case 10:
109
+ if (e_1) throw e_1.error;
110
+ return [7 /*endfinally*/];
111
+ case 11: return [7 /*endfinally*/];
112
+ case 12:
113
+ // Trim trailing newline/carriage-return only; leading/trailing spaces are
114
+ // intentionally preserved in case a password genuinely contains them.
115
+ return [2 /*return*/, data.replace(/[\r\n]+$/g, '')];
116
+ }
117
+ });
118
+ }); };
57
119
  /**
58
120
  * Authenticates against a SASVIYA target using a SAS username and password
59
121
  * (OAuth2 resource owner password grant against the built-in, secret-less
@@ -61,17 +123,35 @@ var utils_2 = require("../../utils");
61
123
  * authenticated commands (`sasjs run`, `sasjs deploy`, etc.) on estates where
62
124
  * no administrator-registered OAuth client/secret is available.
63
125
  *
126
+ * Credentials are resolved in this order (highest precedence first):
127
+ *
128
+ * Username: SAS_USERNAME env var > interactive prompt (TTY only)
129
+ * Password: --password-stdin > SAS_PASSWORD env var > interactive prompt (TTY only)
130
+ *
131
+ * When `--password-stdin` is set, stdin is consumed for the password, so the
132
+ * username MUST come from the SAS_USERNAME env var (interactive prompting is
133
+ * not possible because stdin is already redirected).
134
+ *
135
+ * If no TTY is available and no env var / stdin flag supplies the credential,
136
+ * the function throws with a message pointing the caller at the env vars or
137
+ * `--password-stdin` flag — this makes `sasjs auth login` safe to call from
138
+ * CI pipelines and non-interactive agents.
139
+ *
64
140
  * The password is used only to mint the tokens and is never stored. When the
65
141
  * access token expires, re-run `sasjs auth login -t <target>`.
66
142
  * @param {Target} target - the target to authenticate against.
67
143
  * @param {boolean} insecure - when true, bypasses TLS certificate validation
68
144
  * (for self-signed cert Viya servers). Mirrors the `--insecure` flag on
69
145
  * `sasjs add cred`.
146
+ * @param {boolean} passwordStdin - when true, reads the password from stdin
147
+ * (like `docker login --password-stdin`) instead of prompting or reading
148
+ * the SAS_PASSWORD env var. The username must then come from SAS_USERNAME.
70
149
  */
71
- var authLogin = function (target, insecure) {
150
+ var authLogin = function (target, insecure, passwordStdin) {
72
151
  if (insecure === void 0) { insecure = false; }
152
+ if (passwordStdin === void 0) { passwordStdin = false; }
73
153
  return __awaiter(void 0, void 0, void 0, function () {
74
- var user, pass, _a, access_token, refresh_token, _b, id, name;
154
+ var isTty, envUser, envPass, user, hint, pass, promptedPass, _a, access_token, refresh_token, _b, id, name;
75
155
  var _c, _d, _e;
76
156
  return __generator(this, function (_f) {
77
157
  switch (_f.label) {
@@ -88,17 +168,39 @@ var authLogin = function (target, insecure) {
88
168
  target = new utils_1.Target(__assign(__assign({}, target.toJson(false)), { httpsAgentOptions: __assign(__assign({}, target.httpsAgentOptions), { allowInsecureRequests: true, rejectUnauthorized: false }) }));
89
169
  (_c = process.logger) === null || _c === void 0 ? void 0 : _c.warn('Executing with insecure connection.');
90
170
  }
91
- return [4 /*yield*/, (0, utils_1.getString)('Please enter your SAS username', function (v) { return !!v || 'Username is required.'; })
92
- // Password is prompted with a masked input (prompts 'password' type) rather
93
- // than getString from @sasjs/utils, because getString has no mask/hide mode
94
- // and would echo the password to the terminal.
95
- // The password stays in memory for the remainder of the process. This is an
96
- // accepted trade-off for a short-lived CLI: the process exits within seconds
97
- // and Node's V8 heap is not accessible to other users. Clearing the variable
98
- // would not reliably zero the underlying V8 string storage anyway.
99
- ];
171
+ isTty = !!process.stdin.isTTY;
172
+ envUser = process.env.SAS_USERNAME;
173
+ envPass = process.env.SAS_PASSWORD;
174
+ if (!envUser) return [3 /*break*/, 1];
175
+ user = envUser;
176
+ return [3 /*break*/, 4];
100
177
  case 1:
178
+ if (!(isTty && !passwordStdin)) return [3 /*break*/, 3];
179
+ return [4 /*yield*/, (0, utils_1.getString)('Please enter your SAS username', function (v) { return !!v || 'Username is required.'; })];
180
+ case 2:
101
181
  user = _f.sent();
182
+ return [3 /*break*/, 4];
183
+ case 3:
184
+ hint = passwordStdin
185
+ ? "When using --password-stdin, the username must be provided via the SAS_USERNAME environment variable (stdin is used for the password)."
186
+ : "No TTY is available for an interactive prompt. Set the SAS_USERNAME environment variable (and SAS_PASSWORD, or use --password-stdin).";
187
+ throw new Error("A SAS username is required but was not provided. ".concat(hint));
188
+ case 4:
189
+ if (!passwordStdin) return [3 /*break*/, 6];
190
+ return [4 /*yield*/, readPasswordFromStdin()];
191
+ case 5:
192
+ pass = _f.sent();
193
+ if (!pass) {
194
+ throw new Error("--password-stdin was specified but no password was read from stdin. " +
195
+ "Pipe the password, e.g. echo \"$SAS_PASSWORD\" | sasjs auth login --password-stdin -t ".concat(target.name, "."));
196
+ }
197
+ return [3 /*break*/, 10];
198
+ case 6:
199
+ if (!envPass) return [3 /*break*/, 7];
200
+ pass = envPass;
201
+ return [3 /*break*/, 10];
202
+ case 7:
203
+ if (!isTty) return [3 /*break*/, 9];
102
204
  return [4 /*yield*/, (0, prompts_1.default)({
103
205
  type: 'password',
104
206
  name: 'pass',
@@ -108,16 +210,21 @@ var authLogin = function (target, insecure) {
108
210
  throw new Error('Input cancelled.');
109
211
  }
110
212
  })];
111
- case 2:
112
- pass = (_f.sent()).pass;
113
- return [4 /*yield*/, (0, utils_2.getTokensWithPasswordGrant)(target, user, pass)];
114
- case 3:
213
+ case 8:
214
+ promptedPass = (_f.sent()).pass;
215
+ pass = promptedPass;
216
+ return [3 /*break*/, 10];
217
+ case 9: throw new Error("A SAS password is required but was not provided. " +
218
+ "Set the SAS_PASSWORD environment variable, or pipe the password and use --password-stdin " +
219
+ "(e.g. echo \"$SAS_PASSWORD\" | sasjs auth login --password-stdin -t ".concat(target.name, ")."));
220
+ case 10: return [4 /*yield*/, (0, utils_2.getTokensWithPasswordGrant)(target, user, pass)];
221
+ case 11:
115
222
  _a = _f.sent(), access_token = _a.access_token, refresh_token = _a.refresh_token;
116
223
  return [4 /*yield*/, (0, utils_2.fetchLoggedInUser)(target, access_token)];
117
- case 4:
224
+ case 12:
118
225
  _b = _f.sent(), id = _b.id, name = _b.name;
119
226
  return [4 /*yield*/, (0, utils_2.saveTokens)(target.name, access_token, refresh_token || '')];
120
- case 5:
227
+ case 13:
121
228
  _f.sent();
122
229
  (_d = process.logger) === null || _d === void 0 ? void 0 : _d.success("Logged in as ".concat(id || 'unknown user').concat(name ? " (".concat(name, ")") : '', " on ").concat(target.serverUrl, "."));
123
230
  (_e = process.logger) === null || _e === void 0 ? void 0 : _e.info("The access token is valid for the duration configured on the server (12 hours by default). " +
@@ -139,7 +139,7 @@ describe('AuthCommand', function () {
139
139
  return [4 /*yield*/, command.execute()];
140
140
  case 1:
141
141
  returnCode = _a.sent();
142
- expect(authLoginModule.authLogin).toHaveBeenCalledWith(target, false);
142
+ expect(authLoginModule.authLogin).toHaveBeenCalledWith(target, false, false);
143
143
  expect(returnCode).toEqual(command_1.ReturnCode.Success);
144
144
  return [2 /*return*/];
145
145
  }
@@ -158,7 +158,58 @@ describe('AuthCommand', function () {
158
158
  return [4 /*yield*/, command.execute()];
159
159
  case 1:
160
160
  returnCode = _a.sent();
161
- expect(authLoginModule.authLogin).toHaveBeenCalledWith(target, true);
161
+ expect(authLoginModule.authLogin).toHaveBeenCalledWith(target, true, false);
162
+ expect(returnCode).toEqual(command_1.ReturnCode.Success);
163
+ return [2 /*return*/];
164
+ }
165
+ });
166
+ }); });
167
+ it('should pass passwordStdin=true to authLogin when --password-stdin is provided', function () { return __awaiter(void 0, void 0, void 0, function () {
168
+ var args, command, returnCode;
169
+ return __generator(this, function (_a) {
170
+ switch (_a.label) {
171
+ case 0:
172
+ jest
173
+ .spyOn(authLoginModule, 'authLogin')
174
+ .mockImplementation(function () { return Promise.resolve(); });
175
+ args = __spreadArray(__spreadArray([], __read(defaultArgs), false), [
176
+ 'auth',
177
+ 'login',
178
+ '-t',
179
+ 'test',
180
+ '--password-stdin'
181
+ ], false);
182
+ command = new authCommand_1.AuthCommand(args);
183
+ return [4 /*yield*/, command.execute()];
184
+ case 1:
185
+ returnCode = _a.sent();
186
+ expect(authLoginModule.authLogin).toHaveBeenCalledWith(target, false, true);
187
+ expect(returnCode).toEqual(command_1.ReturnCode.Success);
188
+ return [2 /*return*/];
189
+ }
190
+ });
191
+ }); });
192
+ it('should pass both insecure and passwordStdin flags to authLogin', function () { return __awaiter(void 0, void 0, void 0, function () {
193
+ var args, command, returnCode;
194
+ return __generator(this, function (_a) {
195
+ switch (_a.label) {
196
+ case 0:
197
+ jest
198
+ .spyOn(authLoginModule, 'authLogin')
199
+ .mockImplementation(function () { return Promise.resolve(); });
200
+ args = __spreadArray(__spreadArray([], __read(defaultArgs), false), [
201
+ 'auth',
202
+ 'login',
203
+ '-t',
204
+ 'test',
205
+ '--insecure',
206
+ '--password-stdin'
207
+ ], false);
208
+ command = new authCommand_1.AuthCommand(args);
209
+ return [4 /*yield*/, command.execute()];
210
+ case 1:
211
+ returnCode = _a.sent();
212
+ expect(authLoginModule.authLogin).toHaveBeenCalledWith(target, true, true);
162
213
  expect(returnCode).toEqual(command_1.ReturnCode.Success);
163
214
  return [2 /*return*/];
164
215
  }
@@ -125,9 +125,24 @@ describe('authLogin', function () {
125
125
  name: 'SAS Test User'
126
126
  });
127
127
  utilsModule.saveTokens.mockResolvedValue(undefined);
128
+ // Reset env vars and TTY between tests.
129
+ delete process.env.SAS_USERNAME;
130
+ delete process.env.SAS_PASSWORD;
131
+ // Default to TTY=true so interactive prompts are allowed unless a test
132
+ // explicitly overrides.
133
+ Object.defineProperty(process.stdin, 'isTTY', {
134
+ value: true,
135
+ configurable: true
136
+ });
128
137
  });
129
138
  afterAll(function () {
130
139
  jest.restoreAllMocks();
140
+ delete process.env.SAS_USERNAME;
141
+ delete process.env.SAS_PASSWORD;
142
+ Object.defineProperty(process.stdin, 'isTTY', {
143
+ value: true,
144
+ configurable: true
145
+ });
131
146
  });
132
147
  // ---------------------------------------------------------------------------
133
148
  // Guard 1: Non-Viya rejection
@@ -361,4 +376,290 @@ describe('authLogin', function () {
361
376
  }
362
377
  });
363
378
  }); });
379
+ // ===========================================================================
380
+ // Non-interactive credential input paths (env vars, stdin, no-TTY fallback)
381
+ // ===========================================================================
382
+ // ---------------------------------------------------------------------------
383
+ // Env vars: SAS_USERNAME + SAS_PASSWORD skip both prompts
384
+ // ---------------------------------------------------------------------------
385
+ it('should use SAS_USERNAME and SAS_PASSWORD env vars without prompting', function () { return __awaiter(void 0, void 0, void 0, function () {
386
+ return __generator(this, function (_a) {
387
+ switch (_a.label) {
388
+ case 0:
389
+ process.env.SAS_USERNAME = 'env-user';
390
+ process.env.SAS_PASSWORD = 'env-pass';
391
+ return [4 /*yield*/, authLoginModule.authLogin(viyaTarget, false)];
392
+ case 1:
393
+ _a.sent();
394
+ expect(utils_2.getString).not.toHaveBeenCalled();
395
+ expect(prompts_1.default).not.toHaveBeenCalled();
396
+ expect(utilsModule.getTokensWithPasswordGrant).toHaveBeenCalledWith(viyaTarget, 'env-user', 'env-pass');
397
+ expect(utilsModule.saveTokens).toHaveBeenCalledWith(viyaTarget.name, 'access-123', 'refresh-456');
398
+ return [2 /*return*/];
399
+ }
400
+ });
401
+ }); });
402
+ // ---------------------------------------------------------------------------
403
+ // Env var username only — password still prompted interactively
404
+ // ---------------------------------------------------------------------------
405
+ it('should use SAS_USERNAME but prompt for password when only username env var is set', function () { return __awaiter(void 0, void 0, void 0, function () {
406
+ return __generator(this, function (_a) {
407
+ switch (_a.label) {
408
+ case 0:
409
+ process.env.SAS_USERNAME = 'env-user';
410
+ return [4 /*yield*/, authLoginModule.authLogin(viyaTarget, false)];
411
+ case 1:
412
+ _a.sent();
413
+ expect(utils_2.getString).not.toHaveBeenCalled();
414
+ expect(prompts_1.default).toHaveBeenCalled();
415
+ expect(utilsModule.getTokensWithPasswordGrant).toHaveBeenCalledWith(viyaTarget, 'env-user', 'test-pass');
416
+ return [2 /*return*/];
417
+ }
418
+ });
419
+ }); });
420
+ // ---------------------------------------------------------------------------
421
+ // Env var password only — username still prompted interactively
422
+ // ---------------------------------------------------------------------------
423
+ it('should use SAS_PASSWORD but prompt for username when only password env var is set', function () { return __awaiter(void 0, void 0, void 0, function () {
424
+ return __generator(this, function (_a) {
425
+ switch (_a.label) {
426
+ case 0:
427
+ process.env.SAS_PASSWORD = 'env-pass';
428
+ return [4 /*yield*/, authLoginModule.authLogin(viyaTarget, false)];
429
+ case 1:
430
+ _a.sent();
431
+ expect(utils_2.getString).toHaveBeenCalled();
432
+ expect(prompts_1.default).not.toHaveBeenCalled();
433
+ expect(utilsModule.getTokensWithPasswordGrant).toHaveBeenCalledWith(viyaTarget, 'test-user', 'env-pass');
434
+ return [2 /*return*/];
435
+ }
436
+ });
437
+ }); });
438
+ // ---------------------------------------------------------------------------
439
+ // --password-stdin reads password from stdin, username from env
440
+ // ---------------------------------------------------------------------------
441
+ it('should read password from stdin and username from SAS_USERNAME when --password-stdin is set', function () { return __awaiter(void 0, void 0, void 0, function () {
442
+ var stdinData, originalStdin, mockStdin;
443
+ var _a;
444
+ return __generator(this, function (_b) {
445
+ switch (_b.label) {
446
+ case 0:
447
+ process.env.SAS_USERNAME = 'stdin-user';
448
+ stdinData = 'stdin-pa55\n';
449
+ originalStdin = process.stdin;
450
+ mockStdin = (_a = {},
451
+ _a[Symbol.asyncIterator] = function () {
452
+ var done = false;
453
+ return {
454
+ next: function () {
455
+ if (done)
456
+ return Promise.resolve({ done: true, value: undefined });
457
+ done = true;
458
+ return Promise.resolve({
459
+ done: false,
460
+ value: Buffer.from(stdinData)
461
+ });
462
+ }
463
+ };
464
+ },
465
+ _a);
466
+ Object.defineProperty(process, 'stdin', {
467
+ value: mockStdin,
468
+ configurable: true
469
+ });
470
+ _b.label = 1;
471
+ case 1:
472
+ _b.trys.push([1, , 3, 4]);
473
+ return [4 /*yield*/, authLoginModule.authLogin(viyaTarget, false, true)];
474
+ case 2:
475
+ _b.sent();
476
+ expect(utils_2.getString).not.toHaveBeenCalled();
477
+ expect(prompts_1.default).not.toHaveBeenCalled();
478
+ expect(utilsModule.getTokensWithPasswordGrant).toHaveBeenCalledWith(viyaTarget, 'stdin-user', 'stdin-pa55');
479
+ expect(utilsModule.saveTokens).toHaveBeenCalledWith(viyaTarget.name, 'access-123', 'refresh-456');
480
+ return [3 /*break*/, 4];
481
+ case 3:
482
+ Object.defineProperty(process, 'stdin', {
483
+ value: originalStdin,
484
+ configurable: true
485
+ });
486
+ return [7 /*endfinally*/];
487
+ case 4: return [2 /*return*/];
488
+ }
489
+ });
490
+ }); });
491
+ // ---------------------------------------------------------------------------
492
+ // --password-stdin with empty stdin throws a clear error
493
+ // ---------------------------------------------------------------------------
494
+ it('should throw when --password-stdin is set but stdin is empty', function () { return __awaiter(void 0, void 0, void 0, function () {
495
+ var originalStdin, mockStdin;
496
+ var _a;
497
+ return __generator(this, function (_b) {
498
+ switch (_b.label) {
499
+ case 0:
500
+ process.env.SAS_USERNAME = 'stdin-user';
501
+ originalStdin = process.stdin;
502
+ mockStdin = (_a = {},
503
+ _a[Symbol.asyncIterator] = function () {
504
+ return {
505
+ next: function () {
506
+ return Promise.resolve({ done: true, value: undefined });
507
+ }
508
+ };
509
+ },
510
+ _a);
511
+ Object.defineProperty(process, 'stdin', {
512
+ value: mockStdin,
513
+ configurable: true
514
+ });
515
+ _b.label = 1;
516
+ case 1:
517
+ _b.trys.push([1, , 3, 4]);
518
+ return [4 /*yield*/, expect(authLoginModule.authLogin(viyaTarget, false, true)).rejects.toThrow(/no password was read from stdin/)];
519
+ case 2:
520
+ _b.sent();
521
+ expect(utilsModule.getTokensWithPasswordGrant).not.toHaveBeenCalled();
522
+ expect(utilsModule.saveTokens).not.toHaveBeenCalled();
523
+ return [3 /*break*/, 4];
524
+ case 3:
525
+ Object.defineProperty(process, 'stdin', {
526
+ value: originalStdin,
527
+ configurable: true
528
+ });
529
+ return [7 /*endfinally*/];
530
+ case 4: return [2 /*return*/];
531
+ }
532
+ });
533
+ }); });
534
+ // ---------------------------------------------------------------------------
535
+ // --password-stdin without SAS_USERNAME throws (can't prompt interactively)
536
+ // ---------------------------------------------------------------------------
537
+ it('should throw when --password-stdin is set but SAS_USERNAME is not set', function () { return __awaiter(void 0, void 0, void 0, function () {
538
+ var originalStdin, mockStdin;
539
+ var _a;
540
+ return __generator(this, function (_b) {
541
+ switch (_b.label) {
542
+ case 0:
543
+ originalStdin = process.stdin;
544
+ mockStdin = (_a = {},
545
+ _a[Symbol.asyncIterator] = function () {
546
+ return {
547
+ next: function () {
548
+ return Promise.resolve({ done: true, value: undefined });
549
+ }
550
+ };
551
+ },
552
+ _a);
553
+ Object.defineProperty(process, 'stdin', {
554
+ value: mockStdin,
555
+ configurable: true
556
+ });
557
+ _b.label = 1;
558
+ case 1:
559
+ _b.trys.push([1, , 3, 4]);
560
+ return [4 /*yield*/, expect(authLoginModule.authLogin(viyaTarget, false, true)).rejects.toThrow(/SAS_USERNAME/)];
561
+ case 2:
562
+ _b.sent();
563
+ expect(utilsModule.getTokensWithPasswordGrant).not.toHaveBeenCalled();
564
+ return [3 /*break*/, 4];
565
+ case 3:
566
+ Object.defineProperty(process, 'stdin', {
567
+ value: originalStdin,
568
+ configurable: true
569
+ });
570
+ return [7 /*endfinally*/];
571
+ case 4: return [2 /*return*/];
572
+ }
573
+ });
574
+ }); });
575
+ // ---------------------------------------------------------------------------
576
+ // No TTY, no env vars, no --password-stdin → clear error for username
577
+ // ---------------------------------------------------------------------------
578
+ it('should throw a clear error when no TTY and no env vars are available (username)', function () { return __awaiter(void 0, void 0, void 0, function () {
579
+ return __generator(this, function (_a) {
580
+ switch (_a.label) {
581
+ case 0:
582
+ Object.defineProperty(process.stdin, 'isTTY', {
583
+ value: false,
584
+ configurable: true
585
+ });
586
+ return [4 /*yield*/, expect(authLoginModule.authLogin(viyaTarget, false)).rejects.toThrow(/A SAS username is required/)];
587
+ case 1:
588
+ _a.sent();
589
+ expect(utilsModule.getTokensWithPasswordGrant).not.toHaveBeenCalled();
590
+ return [2 /*return*/];
591
+ }
592
+ });
593
+ }); });
594
+ // ---------------------------------------------------------------------------
595
+ // No TTY + SAS_USERNAME set but no password → clear error for password
596
+ // ---------------------------------------------------------------------------
597
+ it('should throw a clear error when SAS_USERNAME is set but no password source is available', function () { return __awaiter(void 0, void 0, void 0, function () {
598
+ return __generator(this, function (_a) {
599
+ switch (_a.label) {
600
+ case 0:
601
+ Object.defineProperty(process.stdin, 'isTTY', {
602
+ value: false,
603
+ configurable: true
604
+ });
605
+ process.env.SAS_USERNAME = 'env-user';
606
+ return [4 /*yield*/, expect(authLoginModule.authLogin(viyaTarget, false)).rejects.toThrow(/A SAS password is required/)];
607
+ case 1:
608
+ _a.sent();
609
+ expect(utilsModule.getTokensWithPasswordGrant).not.toHaveBeenCalled();
610
+ return [2 /*return*/];
611
+ }
612
+ });
613
+ }); });
614
+ // ---------------------------------------------------------------------------
615
+ // Precedence: --password-stdin takes precedence over SAS_PASSWORD
616
+ // ---------------------------------------------------------------------------
617
+ it('should prefer --password-stdin over SAS_PASSWORD env var', function () { return __awaiter(void 0, void 0, void 0, function () {
618
+ var stdinData, originalStdin, mockStdin;
619
+ var _a;
620
+ return __generator(this, function (_b) {
621
+ switch (_b.label) {
622
+ case 0:
623
+ process.env.SAS_USERNAME = 'env-user';
624
+ process.env.SAS_PASSWORD = 'env-pass';
625
+ stdinData = 'stdin-pa55\n';
626
+ originalStdin = process.stdin;
627
+ mockStdin = (_a = {},
628
+ _a[Symbol.asyncIterator] = function () {
629
+ var done = false;
630
+ return {
631
+ next: function () {
632
+ if (done)
633
+ return Promise.resolve({ done: true, value: undefined });
634
+ done = true;
635
+ return Promise.resolve({
636
+ done: false,
637
+ value: Buffer.from(stdinData)
638
+ });
639
+ }
640
+ };
641
+ },
642
+ _a);
643
+ Object.defineProperty(process, 'stdin', {
644
+ value: mockStdin,
645
+ configurable: true
646
+ });
647
+ _b.label = 1;
648
+ case 1:
649
+ _b.trys.push([1, , 3, 4]);
650
+ return [4 /*yield*/, authLoginModule.authLogin(viyaTarget, false, true)];
651
+ case 2:
652
+ _b.sent();
653
+ expect(utilsModule.getTokensWithPasswordGrant).toHaveBeenCalledWith(viyaTarget, 'env-user', 'stdin-pa55');
654
+ return [3 /*break*/, 4];
655
+ case 3:
656
+ Object.defineProperty(process, 'stdin', {
657
+ value: originalStdin,
658
+ configurable: true
659
+ });
660
+ return [7 /*endfinally*/];
661
+ case 4: return [2 /*return*/];
662
+ }
663
+ });
664
+ }); });
364
665
  });
@@ -69,7 +69,7 @@ var executeFlow = function (flow, sasjs, pollOptions, target, authConfig, csvFil
69
69
  return [4 /*yield*/, sasjs
70
70
  .startComputeJob(jobLocation, null, {
71
71
  contextName: target.contextName
72
- }, authConfig, true, pollOptions, true, job.macroVars)
72
+ }, authConfig, true, pollOptions, true, job.macroVars, undefined, (0, utils_1.persistTokensRefreshedByAdapter)(target))
73
73
  .catch(function (err) { return __awaiter(void 0, void 0, void 0, function () {
74
74
  var logName, predecessors, data;
75
75
  var _a, _b, _c, _d;
@@ -105,7 +105,7 @@ function executeJobViya(sasjs, authConfig, jobPath, target, waitForJob, output,
105
105
  }
106
106
  _c.label = 5;
107
107
  case 5: return [4 /*yield*/, sasjs
108
- .startComputeJob(jobPath, null, { contextName: contextName }, authConfig, waitForJob || !!logFile, pollOptions, true, macroVars === null || macroVars === void 0 ? void 0 : macroVars.macroVars)
108
+ .startComputeJob(jobPath, null, { contextName: contextName }, authConfig, waitForJob || !!logFile, pollOptions, true, macroVars === null || macroVars === void 0 ? void 0 : macroVars.macroVars, undefined, (0, utils_2.persistTokensRefreshedByAdapter)(target))
109
109
  .catch(function (err) { return __awaiter(_this, void 0, void 0, function () {
110
110
  var logData;
111
111
  var _a, _b;
@@ -68,6 +68,7 @@ var types_1 = require("@sasjs/utils/types");
68
68
  var path_1 = __importDefault(require("path"));
69
69
  var utils_2 = require("../../../utils");
70
70
  var utilsModule = __importStar(require("../../../utils/utils"));
71
+ var configUtils = __importStar(require("../../../utils/config"));
71
72
  var execute_1 = require("../internal/execute");
72
73
  var mocks_1 = require("./mocks");
73
74
  var sasjs = new node_1.default();
@@ -104,7 +105,7 @@ describe('executeJobViya', function () {
104
105
  pollInterval: 0,
105
106
  streamLog: true,
106
107
  logFolderPath: testLogsPath
107
- }, true, undefined);
108
+ }, true, undefined, undefined, expect.any(Function));
108
109
  return [4 /*yield*/, (0, utils_1.deleteFile)(testFilePath)];
109
110
  case 2:
110
111
  _a.sent();
@@ -125,7 +126,7 @@ describe('executeJobViya', function () {
125
126
  pollInterval: 0,
126
127
  streamLog: false,
127
128
  logFolderPath: testLogsPath
128
- }, true, undefined);
129
+ }, true, undefined, undefined, expect.any(Function));
129
130
  return [4 /*yield*/, (0, utils_1.deleteFile)(testFilePath)];
130
131
  case 2:
131
132
  _a.sent();
@@ -133,6 +134,27 @@ describe('executeJobViya', function () {
133
134
  }
134
135
  });
135
136
  }); });
137
+ it('should pass persistTokensRefreshedByAdapter(target) as onTokensRefreshed', function () { return __awaiter(void 0, void 0, void 0, function () {
138
+ var spy, callArgs, onTokensRefreshed;
139
+ return __generator(this, function (_a) {
140
+ switch (_a.label) {
141
+ case 0:
142
+ spy = jest
143
+ .spyOn(configUtils, 'persistTokensRefreshedByAdapter')
144
+ .mockImplementation(function () { return jest.fn(); });
145
+ return [4 /*yield*/, (0, execute_1.executeJobViya)(sasjs, mocks_1.mockAuthConfig, 'test/job', target, false, false, undefined, undefined, false, undefined, false)];
146
+ case 1:
147
+ _a.sent();
148
+ expect(spy).toHaveBeenCalledWith(target);
149
+ callArgs = sasjs.startComputeJob.mock.calls[0];
150
+ onTokensRefreshed = callArgs[callArgs.length - 1];
151
+ expect(onTokensRefreshed).toBeDefined();
152
+ expect(typeof onTokensRefreshed).toEqual('function');
153
+ spy.mockRestore();
154
+ return [2 /*return*/];
155
+ }
156
+ });
157
+ }); });
136
158
  });
137
159
  describe('executeJobSasjs', function () {
138
160
  beforeEach(function () { return __awaiter(void 0, void 0, void 0, function () {
package/package.json CHANGED
@@ -69,7 +69,7 @@
69
69
  "registry": "https://registry.npmjs.org"
70
70
  },
71
71
  "dependencies": {
72
- "@sasjs/adapter": "^4.18.0",
72
+ "@sasjs/adapter": "^4.19.0",
73
73
  "@sasjs/core": "4.68.1",
74
74
  "@sasjs/lint": "2.4.3",
75
75
  "@sasjs/utils": "^3.6.0",
@@ -124,5 +124,5 @@
124
124
  "ws": "8.21.0",
125
125
  "nanoid": "3.3.18"
126
126
  },
127
- "version": "4.19.0"
127
+ "version": "4.20.1"
128
128
  }