@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.
- package/build/commands/auth/authCommand.d.ts +1 -0
- package/build/commands/auth/authCommand.js +28 -2
- package/build/commands/auth/login.d.ts +18 -1
- package/build/commands/auth/login.js +124 -17
- package/build/commands/auth/spec/authCommand.spec.js +53 -2
- package/build/commands/auth/spec/login.spec.js +301 -0
- package/build/commands/flow/internal/executeFlow.js +1 -1
- package/build/commands/job/internal/execute/viya.js +1 -1
- package/build/commands/job/spec/execute.spec.js +24 -2
- package/package.json +2 -2
|
@@ -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
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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
|
|
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
|
|
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.
|
|
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.
|
|
127
|
+
"version": "4.20.1"
|
|
128
128
|
}
|