@sasjs/cli 4.18.0 → 4.18.2
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/compile/internal/compileTestFile.d.ts +18 -0
- package/build/commands/compile/internal/compileTestFile.js +33 -11
- package/build/commands/compile/internal/spec/compileTestFile.spec.js +117 -0
- package/build/commands/create/spec/create.spec.js +5 -2
- package/build/commands/deploy/spec/cbd.spec.server.viya.js +2 -2
- package/build/commands/docs/generateDocs.js +11 -3
- package/build/commands/docs/spec/generateDocs.spec.js +96 -0
- package/build/commands/init/spec/init.spec.js +5 -2
- package/build/commands/job/spec/jobCommand.spec.js +13 -13
- package/build/commands/shared/deployToSasViyaWithServicePack.js +4 -5
- package/build/commands/shared/spec/deployToSasViyaWithServicePack.spec.d.ts +1 -0
- package/build/commands/shared/spec/deployToSasViyaWithServicePack.spec.js +146 -0
- package/build/doxy/Doxyfile +1 -1
- package/build/types/command/spec/targetCommand.spec.d.ts +1 -0
- package/build/types/command/spec/targetCommand.spec.js +118 -0
- package/build/utils/auth.d.ts +8 -0
- package/build/utils/auth.js +8 -0
- package/build/utils/config.d.ts +29 -1
- package/build/utils/config.js +29 -1
- package/build/utils/spec/utils.spec.js +191 -0
- package/build/utils/utils.js +26 -10
- package/package.json +1 -1
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
import { CompileTree, Target } from '@sasjs/utils';
|
|
2
2
|
import { TestFlow } from '../../../types';
|
|
3
|
+
/**
|
|
4
|
+
* Resolves the relative destination fragment for a compiled test file inside
|
|
5
|
+
* `sasjsbuild/tests` - either just the file's basename (saveToRoot, used for
|
|
6
|
+
* testSetUp/testTearDown), or the path fragment following the build
|
|
7
|
+
* destination folder name (used for service/job test files).
|
|
8
|
+
*
|
|
9
|
+
* `filePath` for testSetUp/testTearDown comes straight from a user's
|
|
10
|
+
* sasjsconfig.json, which conventionally uses forward slashes for
|
|
11
|
+
* portability - it is not guaranteed to match the current OS's separator.
|
|
12
|
+
* Splitting on both `/` and `\` (instead of only `path.sep`) means this
|
|
13
|
+
* works whether the config was authored with POSIX-style paths and the CLI
|
|
14
|
+
* is running on Windows (`path.sep === '\\'`) or vice versa. Splitting on
|
|
15
|
+
* `path.sep` alone silently no-ops when the string contains none of that
|
|
16
|
+
* separator, leaving the whole config path intact and producing a nested
|
|
17
|
+
* `sasjsbuild/tests/sasjs/tests/testsetup.sas` instead of a flat
|
|
18
|
+
* `sasjsbuild/tests/testsetup.sas`.
|
|
19
|
+
*/
|
|
20
|
+
export declare const getTestFileDestinationFragment: (filePath: string, buildDestinationFolderName: string | undefined, saveToRoot: boolean) => string;
|
|
3
21
|
export declare function compileTestFile(target: Target, filePath: string, testVar: string | undefined, saveToRoot: boolean | undefined, removeOriginalFile: boolean | undefined, compileTree: CompileTree, destinationPath?: string): Promise<void>;
|
|
4
22
|
export declare function copyTestMacroFiles(folderAbsolutePath: string): Promise<void>;
|
|
5
23
|
export declare const compileTestFlow: (target: Target) => Promise<TestFlow | undefined>;
|
|
@@ -64,7 +64,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
64
64
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
65
65
|
};
|
|
66
66
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
67
|
-
exports.compileTestFlow = exports.copyTestMacroFiles = exports.compileTestFile = void 0;
|
|
67
|
+
exports.compileTestFlow = exports.copyTestMacroFiles = exports.compileTestFile = exports.getTestFileDestinationFragment = void 0;
|
|
68
68
|
var utils_1 = require("@sasjs/utils");
|
|
69
69
|
var chalk_1 = __importDefault(require("chalk"));
|
|
70
70
|
var path_1 = __importDefault(require("path"));
|
|
@@ -73,6 +73,37 @@ var config_1 = require("../../../utils/config");
|
|
|
73
73
|
var file_1 = require("../../../utils/file");
|
|
74
74
|
var _1 = require("./");
|
|
75
75
|
var getFileName = function (filePath) { return path_1.default.parse(filePath).base; };
|
|
76
|
+
/**
|
|
77
|
+
* Resolves the relative destination fragment for a compiled test file inside
|
|
78
|
+
* `sasjsbuild/tests` - either just the file's basename (saveToRoot, used for
|
|
79
|
+
* testSetUp/testTearDown), or the path fragment following the build
|
|
80
|
+
* destination folder name (used for service/job test files).
|
|
81
|
+
*
|
|
82
|
+
* `filePath` for testSetUp/testTearDown comes straight from a user's
|
|
83
|
+
* sasjsconfig.json, which conventionally uses forward slashes for
|
|
84
|
+
* portability - it is not guaranteed to match the current OS's separator.
|
|
85
|
+
* Splitting on both `/` and `\` (instead of only `path.sep`) means this
|
|
86
|
+
* works whether the config was authored with POSIX-style paths and the CLI
|
|
87
|
+
* is running on Windows (`path.sep === '\\'`) or vice versa. Splitting on
|
|
88
|
+
* `path.sep` alone silently no-ops when the string contains none of that
|
|
89
|
+
* separator, leaving the whole config path intact and producing a nested
|
|
90
|
+
* `sasjsbuild/tests/sasjs/tests/testsetup.sas` instead of a flat
|
|
91
|
+
* `sasjsbuild/tests/testsetup.sas`.
|
|
92
|
+
*/
|
|
93
|
+
var getTestFileDestinationFragment = function (filePath, buildDestinationFolderName, saveToRoot) {
|
|
94
|
+
var segments = filePath.split(/[\\/]/);
|
|
95
|
+
var destinationFragment = saveToRoot
|
|
96
|
+
? segments.pop() || ''
|
|
97
|
+
: segments
|
|
98
|
+
.reduce(function (acc, item, i, arr) {
|
|
99
|
+
return acc.length
|
|
100
|
+
? __spreadArray(__spreadArray([], __read(acc), false), [item], false) : arr[i - 1] === buildDestinationFolderName
|
|
101
|
+
? __spreadArray(__spreadArray([], __read(acc), false), [item], false) : acc;
|
|
102
|
+
}, [])
|
|
103
|
+
.join(path_1.default.sep);
|
|
104
|
+
return destinationFragment;
|
|
105
|
+
};
|
|
106
|
+
exports.getTestFileDestinationFragment = getTestFileDestinationFragment;
|
|
76
107
|
function compileTestFile(target, filePath, testVar, saveToRoot, removeOriginalFile, compileTree, destinationPath) {
|
|
77
108
|
if (testVar === void 0) { testVar = ''; }
|
|
78
109
|
if (saveToRoot === void 0) { saveToRoot = true; }
|
|
@@ -100,16 +131,7 @@ function compileTestFile(target, filePath, testVar, saveToRoot, removeOriginalFi
|
|
|
100
131
|
.pop();
|
|
101
132
|
destinationPath =
|
|
102
133
|
destinationPath ||
|
|
103
|
-
path_1.default.join(buildDestinationTestFolder, saveToRoot
|
|
104
|
-
? filePath.split(path_1.default.sep).pop() || ''
|
|
105
|
-
: filePath
|
|
106
|
-
.split(path_1.default.sep)
|
|
107
|
-
.reduce(function (acc, item, i, arr) {
|
|
108
|
-
return acc.length
|
|
109
|
-
? __spreadArray(__spreadArray([], __read(acc), false), [item], false) : arr[i - 1] === buildDestinationFolderName
|
|
110
|
-
? __spreadArray(__spreadArray([], __read(acc), false), [item], false) : acc;
|
|
111
|
-
}, [])
|
|
112
|
-
.join(path_1.default.sep));
|
|
134
|
+
path_1.default.join(buildDestinationTestFolder, (0, exports.getTestFileDestinationFragment)(filePath, buildDestinationFolderName, saveToRoot));
|
|
113
135
|
return [4 /*yield*/, (0, utils_1.createFile)(destinationPath, dependencies)];
|
|
114
136
|
case 4:
|
|
115
137
|
_d.sent();
|
|
@@ -56,6 +56,31 @@ var test_1 = require("../../../../utils/test");
|
|
|
56
56
|
var path_1 = __importDefault(require("path"));
|
|
57
57
|
var compile_1 = require("../../compile");
|
|
58
58
|
var chalk_1 = __importDefault(require("chalk"));
|
|
59
|
+
describe('getTestFileDestinationFragment', function () {
|
|
60
|
+
// sasjsconfig.json testConfig paths (testSetUp/testTearDown) are conventionally
|
|
61
|
+
// forward-slash, regardless of the OS the CLI is running on - these tests pin
|
|
62
|
+
// that a Windows-style host separator doesn't break the flattening/reduce logic
|
|
63
|
+
// when the config value itself uses forward slashes (see PLAN-windows-testsetup-path.md).
|
|
64
|
+
describe('saveToRoot = true (testSetUp/testTearDown)', function () {
|
|
65
|
+
it('flattens a forward-slash config path to its basename', function () {
|
|
66
|
+
expect((0, __1.getTestFileDestinationFragment)('sasjs/tests/testsetup.sas', 'tests', true)).toEqual('testsetup.sas');
|
|
67
|
+
});
|
|
68
|
+
it('flattens a backslash (Windows-native) path to its basename', function () {
|
|
69
|
+
expect((0, __1.getTestFileDestinationFragment)('sasjs\\tests\\testsetup.sas', 'tests', true)).toEqual('testsetup.sas');
|
|
70
|
+
});
|
|
71
|
+
it('returns an empty string for a path ending in a separator', function () {
|
|
72
|
+
expect((0, __1.getTestFileDestinationFragment)('sasjs/tests/', 'tests', true)).toEqual('');
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
describe('saveToRoot = false (service/job test files)', function () {
|
|
76
|
+
it('keeps the path fragment after the build destination folder name, for a forward-slash path', function () {
|
|
77
|
+
expect((0, __1.getTestFileDestinationFragment)('sasjsbuild/tests/services/admin/random.test.sas', 'tests', false)).toEqual(['services', 'admin', 'random.test.sas'].join(path_1.default.sep));
|
|
78
|
+
});
|
|
79
|
+
it('keeps the path fragment after the build destination folder name, for a backslash path', function () {
|
|
80
|
+
expect((0, __1.getTestFileDestinationFragment)('sasjsbuild\\tests\\services\\admin\\random.test.sas', 'tests', false)).toEqual(['services', 'admin', 'random.test.sas'].join(path_1.default.sep));
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
});
|
|
59
84
|
describe('compileTestFile', function () {
|
|
60
85
|
var appName = "cli-tests-compile-test-file-".concat((0, utils_1.generateTimestamp)());
|
|
61
86
|
var temp = (0, test_1.generateTestTarget)(appName, '/Public/app', {
|
|
@@ -159,6 +184,57 @@ describe('compileTestFile', function () {
|
|
|
159
184
|
}
|
|
160
185
|
});
|
|
161
186
|
}); });
|
|
187
|
+
it('should use default testVar/saveToRoot when omitted', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
188
|
+
var compileTree;
|
|
189
|
+
return __generator(this, function (_a) {
|
|
190
|
+
switch (_a.label) {
|
|
191
|
+
case 0: return [4 /*yield*/, (0, compile_1.compile)(target)];
|
|
192
|
+
case 1:
|
|
193
|
+
_a.sent();
|
|
194
|
+
compileTree = (0, __1.getCompileTree)(target);
|
|
195
|
+
return [4 /*yield*/, expect((0, __1.compileTestFile)(target, target.testConfig.testSetUp, undefined, undefined, false, compileTree)).toResolve()];
|
|
196
|
+
case 2:
|
|
197
|
+
_a.sent();
|
|
198
|
+
return [2 /*return*/];
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
}); });
|
|
202
|
+
it('should prepend a non-empty testVar to the compiled test file', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
203
|
+
var compileTree, testVar, compiledTestSetUpPath, compiledContent;
|
|
204
|
+
return __generator(this, function (_a) {
|
|
205
|
+
switch (_a.label) {
|
|
206
|
+
case 0: return [4 /*yield*/, (0, compile_1.compile)(target)];
|
|
207
|
+
case 1:
|
|
208
|
+
_a.sent();
|
|
209
|
+
compileTree = (0, __1.getCompileTree)(target);
|
|
210
|
+
testVar = '%let some_var=some_value;';
|
|
211
|
+
return [4 /*yield*/, (0, __1.compileTestFile)(target, target.testConfig.testSetUp, testVar, true, false, compileTree)];
|
|
212
|
+
case 2:
|
|
213
|
+
_a.sent();
|
|
214
|
+
compiledTestSetUpPath = path_1.default.join(buildPath, 'tests', 'testsetup.sas');
|
|
215
|
+
return [4 /*yield*/, (0, utils_1.readFile)(compiledTestSetUpPath)];
|
|
216
|
+
case 3:
|
|
217
|
+
compiledContent = _a.sent();
|
|
218
|
+
expect(compiledContent.indexOf(testVar)).toEqual(0);
|
|
219
|
+
return [2 /*return*/];
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
}); });
|
|
223
|
+
it('should skip copying a macro test file that is already present in the build folder', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
224
|
+
var macroFolderAbsolutePath;
|
|
225
|
+
return __generator(this, function (_a) {
|
|
226
|
+
switch (_a.label) {
|
|
227
|
+
case 0: return [4 /*yield*/, (0, compile_1.compile)(target)];
|
|
228
|
+
case 1:
|
|
229
|
+
_a.sent();
|
|
230
|
+
macroFolderAbsolutePath = path_1.default.join(__dirname, appName, 'sasjs', 'macros');
|
|
231
|
+
return [4 /*yield*/, expect((0, __1.copyTestMacroFiles)(macroFolderAbsolutePath)).toResolve()];
|
|
232
|
+
case 2:
|
|
233
|
+
_a.sent();
|
|
234
|
+
return [2 /*return*/];
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
}); });
|
|
162
238
|
});
|
|
163
239
|
describe('compileTestFlow', function () {
|
|
164
240
|
it('should compile test flow', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
@@ -198,6 +274,47 @@ describe('compileTestFile', function () {
|
|
|
198
274
|
}
|
|
199
275
|
});
|
|
200
276
|
}); });
|
|
277
|
+
it('should return no tests when the tests folder is empty', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
278
|
+
var testsFolderPath, testFlow;
|
|
279
|
+
return __generator(this, function (_a) {
|
|
280
|
+
switch (_a.label) {
|
|
281
|
+
case 0: return [4 /*yield*/, (0, compile_1.compile)(target)];
|
|
282
|
+
case 1:
|
|
283
|
+
_a.sent();
|
|
284
|
+
testsFolderPath = path_1.default.join(buildPath, 'tests');
|
|
285
|
+
return [4 /*yield*/, (0, utils_1.deleteFolder)(testsFolderPath)];
|
|
286
|
+
case 2:
|
|
287
|
+
_a.sent();
|
|
288
|
+
return [4 /*yield*/, (0, utils_1.createFolder)(testsFolderPath)];
|
|
289
|
+
case 3:
|
|
290
|
+
_a.sent();
|
|
291
|
+
return [4 /*yield*/, (0, __1.compileTestFlow)(target)];
|
|
292
|
+
case 4:
|
|
293
|
+
testFlow = _a.sent();
|
|
294
|
+
expect(testFlow.tests).toEqual([]);
|
|
295
|
+
expect(testFlow.testSetUp).toBeUndefined();
|
|
296
|
+
expect(testFlow.testTearDown).toBeUndefined();
|
|
297
|
+
return [2 /*return*/];
|
|
298
|
+
}
|
|
299
|
+
});
|
|
300
|
+
}); });
|
|
301
|
+
it('should not set testSetUp/testTearDown when the configured file is not among the compiled tests', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
302
|
+
var targetWithBogusSetup, testFlow;
|
|
303
|
+
return __generator(this, function (_a) {
|
|
304
|
+
switch (_a.label) {
|
|
305
|
+
case 0: return [4 /*yield*/, (0, compile_1.compile)(target)];
|
|
306
|
+
case 1:
|
|
307
|
+
_a.sent();
|
|
308
|
+
targetWithBogusSetup = new utils_1.Target(__assign(__assign({}, target.toJson()), { testConfig: __assign(__assign({}, target.testConfig), { testSetUp: 'does-not-exist-setup.sas', testTearDown: 'does-not-exist-teardown.sas' }) }));
|
|
309
|
+
return [4 /*yield*/, (0, __1.compileTestFlow)(targetWithBogusSetup)];
|
|
310
|
+
case 2:
|
|
311
|
+
testFlow = _a.sent();
|
|
312
|
+
expect(testFlow.testSetUp).toBeUndefined();
|
|
313
|
+
expect(testFlow.testTearDown).toBeUndefined();
|
|
314
|
+
return [2 /*return*/];
|
|
315
|
+
}
|
|
316
|
+
});
|
|
317
|
+
}); });
|
|
201
318
|
it('should log coverage', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
202
319
|
var expectedHeader, expectedData;
|
|
203
320
|
return __generator(this, function (_a) {
|
|
@@ -68,10 +68,13 @@ describe('sasjs create', function () {
|
|
|
68
68
|
afterEach(function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
69
69
|
return __generator(this, function (_a) {
|
|
70
70
|
switch (_a.label) {
|
|
71
|
-
case 0:
|
|
71
|
+
case 0:
|
|
72
|
+
if (!process.projectDir) return [3 /*break*/, 2];
|
|
73
|
+
return [4 /*yield*/, (0, utils_1.deleteFolder)(process.projectDir)];
|
|
72
74
|
case 1:
|
|
73
75
|
_a.sent();
|
|
74
|
-
|
|
76
|
+
_a.label = 2;
|
|
77
|
+
case 2: return [2 /*return*/];
|
|
75
78
|
}
|
|
76
79
|
});
|
|
77
80
|
}); });
|
|
@@ -255,13 +255,13 @@ describe('sasjs cbd with Viya', function () {
|
|
|
255
255
|
return __generator(this, function (_a) {
|
|
256
256
|
switch (_a.label) {
|
|
257
257
|
case 0:
|
|
258
|
-
jest.spyOn(configUtils, '
|
|
258
|
+
jest.spyOn(configUtils, 'getAuthConfig').mockImplementation(function () {
|
|
259
259
|
return Promise.reject('Token error');
|
|
260
260
|
});
|
|
261
261
|
return [4 /*yield*/, (0, build_1.build)(target)];
|
|
262
262
|
case 1:
|
|
263
263
|
_a.sent();
|
|
264
|
-
return [4 /*yield*/, expect((0, deploy_1.deploy)(target, true)).rejects.toThrow(new Error("Deployment failed. Request is not authenticated.\
|
|
264
|
+
return [4 /*yield*/, expect((0, deploy_1.deploy)(target, true)).rejects.toThrow(new Error("Deployment failed. Request is not authenticated.\nToken error"))];
|
|
265
265
|
case 2:
|
|
266
266
|
_a.sent();
|
|
267
267
|
jest.restoreAllMocks();
|
|
@@ -95,7 +95,7 @@ var getDocConfig_1 = require("./internal/getDocConfig");
|
|
|
95
95
|
function generateDocs(target, config, outDirectory) {
|
|
96
96
|
var _a, _b;
|
|
97
97
|
return __awaiter(this, void 0, void 0, function () {
|
|
98
|
-
var _c, serverUrl, newOutDirectory, enableLineage, doxyContentFromConfig, _d, macroCoreFolders, macroFolders, programFolders, serviceFolders, jobFolders, combinedFolders, PROJECT_NAME, PROJECT_BRIEF, packageJsonContent, packageJson, e_1, doxyContent, layoutFilePath, LAYOUT_FILE, doxyParams, doxyConfigPath, spinner, pathExists, _e, stderr, code, foldersListForDot;
|
|
98
|
+
var _c, serverUrl, newOutDirectory, enableLineage, doxyContentFromConfig, _d, macroCoreFolders, macroFolders, programFolders, serviceFolders, jobFolders, combinedFolders, PROJECT_NAME, PROJECT_BRIEF, packageJsonContent, packageJson, e_1, doxyContent, layoutFilePath, LAYOUT_FILE, readMePath, doxyParams, doxyConfigPath, spinner, pathExists, _e, stderr, code, foldersListForDot;
|
|
99
99
|
var _this = this;
|
|
100
100
|
return __generator(this, function (_f) {
|
|
101
101
|
switch (_f.label) {
|
|
@@ -153,12 +153,20 @@ function generateDocs(target, config, outDirectory) {
|
|
|
153
153
|
return [2 /*return*/, tmpFilePath];
|
|
154
154
|
}
|
|
155
155
|
});
|
|
156
|
-
}); })
|
|
156
|
+
}); })
|
|
157
|
+
// Doxygen matches USE_MDFILE_AS_MAINPAGE against the file's path exactly as it
|
|
158
|
+
// appears in INPUT - a bare name like "README.md" no longer matches an absolute
|
|
159
|
+
// INPUT path on newer Doxygen versions (see doxygen/doxygen#10110), which silently
|
|
160
|
+
// drops the README content instead of using it as the main page. Passing the same
|
|
161
|
+
// absolute path to both keeps them in sync regardless of Doxygen version.
|
|
162
|
+
];
|
|
157
163
|
case 5:
|
|
158
164
|
LAYOUT_FILE = _f.sent();
|
|
165
|
+
readMePath = path_1.default.join(doxyContent.path, doxyContent.readMe);
|
|
159
166
|
doxyParams = setVariableCmd({
|
|
160
167
|
DOXY_HTML_OUTPUT: newOutDirectory,
|
|
161
|
-
DOXY_INPUT: "\"".concat(
|
|
168
|
+
DOXY_INPUT: "\"".concat(readMePath, "\" ").concat(combinedFolders),
|
|
169
|
+
DOXY_MAINPAGE: readMePath,
|
|
162
170
|
HTML_EXTRA_FILES: "\"".concat(path_1.default.join(doxyContent.path, doxyContent.favIcon), "\""),
|
|
163
171
|
HTML_EXTRA_STYLESHEET: "\"".concat(path_1.default.join(doxyContent.path, doxyContent.stylesheet), "\""),
|
|
164
172
|
HTML_FOOTER: path_1.default.join(doxyContent.path, doxyContent.footer),
|
|
@@ -51,6 +51,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
51
51
|
};
|
|
52
52
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
53
53
|
var path_1 = __importDefault(require("path"));
|
|
54
|
+
var shelljs_1 = __importDefault(require("shelljs"));
|
|
54
55
|
var test_1 = require("../../../utils/test");
|
|
55
56
|
var utils_1 = require("@sasjs/utils");
|
|
56
57
|
var generateDocs_1 = require("../generateDocs");
|
|
@@ -454,4 +455,99 @@ describe('sasjs doc', function () {
|
|
|
454
455
|
}
|
|
455
456
|
});
|
|
456
457
|
}); });
|
|
458
|
+
it('should throw when no macro/program/service/job folders are configured', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
459
|
+
var emptyTarget;
|
|
460
|
+
return __generator(this, function (_a) {
|
|
461
|
+
switch (_a.label) {
|
|
462
|
+
case 0:
|
|
463
|
+
emptyTarget = new utils_1.Target({
|
|
464
|
+
name: 'no-folders',
|
|
465
|
+
appLoc: '/Public/test/',
|
|
466
|
+
serverType: utils_1.ServerType.SasViya,
|
|
467
|
+
docConfig: { displayMacroCore: false }
|
|
468
|
+
});
|
|
469
|
+
return [4 /*yield*/, expect((0, generateDocs_1.generateDocs)(emptyTarget, {
|
|
470
|
+
docConfig: { displayMacroCore: false }
|
|
471
|
+
})).rejects.toThrow('Unable to locate folders for generating docs.')];
|
|
472
|
+
case 1:
|
|
473
|
+
_a.sent();
|
|
474
|
+
return [2 /*return*/];
|
|
475
|
+
}
|
|
476
|
+
});
|
|
477
|
+
}); });
|
|
478
|
+
it('should not toggle the spinner when LOG_LEVEL is Debug', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
479
|
+
var originalLogLevel;
|
|
480
|
+
return __generator(this, function (_a) {
|
|
481
|
+
switch (_a.label) {
|
|
482
|
+
case 0:
|
|
483
|
+
originalLogLevel = process.env.LOG_LEVEL;
|
|
484
|
+
process.env.LOG_LEVEL = 'Debug';
|
|
485
|
+
return [4 /*yield*/, expect((0, generateDocs_1.generateDocs)(defaultTarget, defaultConfig)).resolves.toEqual({
|
|
486
|
+
outDirectory: docOutputDefault
|
|
487
|
+
})];
|
|
488
|
+
case 1:
|
|
489
|
+
_a.sent();
|
|
490
|
+
process.env.LOG_LEVEL = originalLogLevel;
|
|
491
|
+
return [2 /*return*/];
|
|
492
|
+
}
|
|
493
|
+
});
|
|
494
|
+
}); });
|
|
495
|
+
it('should fall back to the default project name when package.json has none', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
496
|
+
var packageJsonPath, originalPackageJson, packageJson;
|
|
497
|
+
return __generator(this, function (_a) {
|
|
498
|
+
switch (_a.label) {
|
|
499
|
+
case 0:
|
|
500
|
+
packageJsonPath = path_1.default.join(__dirname, appName, 'package.json');
|
|
501
|
+
return [4 /*yield*/, (0, utils_1.readFile)(packageJsonPath)];
|
|
502
|
+
case 1:
|
|
503
|
+
originalPackageJson = _a.sent();
|
|
504
|
+
packageJson = JSON.parse(originalPackageJson);
|
|
505
|
+
delete packageJson.name;
|
|
506
|
+
return [4 /*yield*/, (0, utils_1.createFile)(packageJsonPath, JSON.stringify(packageJson))];
|
|
507
|
+
case 2:
|
|
508
|
+
_a.sent();
|
|
509
|
+
return [4 /*yield*/, expect((0, generateDocs_1.generateDocs)(defaultTarget, defaultConfig)).resolves.toEqual({
|
|
510
|
+
outDirectory: docOutputDefault
|
|
511
|
+
})];
|
|
512
|
+
case 3:
|
|
513
|
+
_a.sent();
|
|
514
|
+
return [4 /*yield*/, (0, utils_1.createFile)(packageJsonPath, originalPackageJson)];
|
|
515
|
+
case 4:
|
|
516
|
+
_a.sent();
|
|
517
|
+
return [2 /*return*/];
|
|
518
|
+
}
|
|
519
|
+
});
|
|
520
|
+
}); });
|
|
521
|
+
it('should throw a Doxygen-specific error when the shell command fails with an "error: " stderr', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
522
|
+
return __generator(this, function (_a) {
|
|
523
|
+
switch (_a.label) {
|
|
524
|
+
case 0:
|
|
525
|
+
jest.spyOn(shelljs_1.default, 'exec').mockReturnValueOnce({
|
|
526
|
+
code: 1,
|
|
527
|
+
stderr: 'error: bad config',
|
|
528
|
+
stdout: ''
|
|
529
|
+
});
|
|
530
|
+
return [4 /*yield*/, expect((0, generateDocs_1.generateDocs)(defaultTarget, defaultConfig)).rejects.toThrow('\nerror: bad config')];
|
|
531
|
+
case 1:
|
|
532
|
+
_a.sent();
|
|
533
|
+
return [2 /*return*/];
|
|
534
|
+
}
|
|
535
|
+
});
|
|
536
|
+
}); });
|
|
537
|
+
it('should throw a generic Doxygen-not-installed error for any other shell failure', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
538
|
+
return __generator(this, function (_a) {
|
|
539
|
+
switch (_a.label) {
|
|
540
|
+
case 0:
|
|
541
|
+
jest.spyOn(shelljs_1.default, 'exec').mockReturnValueOnce({
|
|
542
|
+
code: 1,
|
|
543
|
+
stderr: 'command not found',
|
|
544
|
+
stdout: ''
|
|
545
|
+
});
|
|
546
|
+
return [4 /*yield*/, expect((0, generateDocs_1.generateDocs)(defaultTarget, defaultConfig)).rejects.toThrow("The Doxygen application is not installed or configured.")];
|
|
547
|
+
case 1:
|
|
548
|
+
_a.sent();
|
|
549
|
+
return [2 /*return*/];
|
|
550
|
+
}
|
|
551
|
+
});
|
|
552
|
+
}); });
|
|
457
553
|
});
|
|
@@ -54,10 +54,13 @@ describe('sasjs init', function () {
|
|
|
54
54
|
afterEach(function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
55
55
|
return __generator(this, function (_a) {
|
|
56
56
|
switch (_a.label) {
|
|
57
|
-
case 0:
|
|
57
|
+
case 0:
|
|
58
|
+
if (!process.projectDir) return [3 /*break*/, 2];
|
|
59
|
+
return [4 /*yield*/, (0, utils_1.deleteFolder)(process.projectDir)];
|
|
58
60
|
case 1:
|
|
59
61
|
_a.sent();
|
|
60
|
-
|
|
62
|
+
_a.label = 2;
|
|
63
|
+
case 2: return [2 /*return*/];
|
|
61
64
|
}
|
|
62
65
|
});
|
|
63
66
|
}); });
|
|
@@ -392,6 +392,19 @@ describe('JobCommand', function () {
|
|
|
392
392
|
}
|
|
393
393
|
});
|
|
394
394
|
}); });
|
|
395
|
+
it('should log deprecation warning if returnStatusOnly flag is present', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
396
|
+
return __generator(this, function (_a) {
|
|
397
|
+
switch (_a.label) {
|
|
398
|
+
case 0:
|
|
399
|
+
jest.spyOn(process.logger, 'warn');
|
|
400
|
+
return [4 /*yield*/, executeCommandWrapper([jobPath, '--returnStatusOnly'])];
|
|
401
|
+
case 1:
|
|
402
|
+
_a.sent();
|
|
403
|
+
expect(process.logger.warn).toHaveBeenCalledWith('--returnStatusOnly (-r) flag is deprecated.');
|
|
404
|
+
return [2 /*return*/];
|
|
405
|
+
}
|
|
406
|
+
});
|
|
407
|
+
}); });
|
|
395
408
|
});
|
|
396
409
|
describe('for server type sas9', function () {
|
|
397
410
|
beforeEach(function () {
|
|
@@ -549,19 +562,6 @@ describe('JobCommand', function () {
|
|
|
549
562
|
});
|
|
550
563
|
}); });
|
|
551
564
|
});
|
|
552
|
-
it('should log deprecation warning if returnStatusOnly flag is present', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
553
|
-
return __generator(this, function (_a) {
|
|
554
|
-
switch (_a.label) {
|
|
555
|
-
case 0:
|
|
556
|
-
jest.spyOn(process.logger, 'warn');
|
|
557
|
-
return [4 /*yield*/, executeCommandWrapper([jobPath, '--returnStatusOnly'])];
|
|
558
|
-
case 1:
|
|
559
|
-
_a.sent();
|
|
560
|
-
expect(process.logger.warn).toHaveBeenCalledWith('--returnStatusOnly (-r) flag is deprecated.');
|
|
561
|
-
return [2 /*return*/];
|
|
562
|
-
}
|
|
563
|
-
});
|
|
564
|
-
}); });
|
|
565
565
|
});
|
|
566
566
|
var setupMocksForViya = function () {
|
|
567
567
|
jest.resetAllMocks();
|
|
@@ -55,12 +55,11 @@ function deployToSasViyaWithServicePack(jsonFilePath, target, isLocal, isForced)
|
|
|
55
55
|
throw new Error('Provided data file must be valid json.');
|
|
56
56
|
}
|
|
57
57
|
populateCodeInServicePack(jsonObject);
|
|
58
|
-
return [4 /*yield*/, (0, utils_2.
|
|
58
|
+
return [4 /*yield*/, (0, utils_2.getAuthConfig)(target).catch(function (err) {
|
|
59
|
+
throw new Error("Deployment failed. Request is not authenticated.\n".concat((err === null || err === void 0 ? void 0 : err.message) || err));
|
|
60
|
+
})];
|
|
59
61
|
case 2:
|
|
60
|
-
access_token = _a.sent();
|
|
61
|
-
if (!access_token) {
|
|
62
|
-
throw new Error("Deployment failed. Request is not authenticated.\nPlease add the following variables to your .env".concat(isLocal ? ".".concat(target.name) : '', " file:\nCLIENT, SECRET, ACCESS_TOKEN, REFRESH_TOKEN"));
|
|
63
|
-
}
|
|
62
|
+
access_token = (_a.sent()).access_token;
|
|
64
63
|
sasjs = (0, utils_2.getSASjs)(target);
|
|
65
64
|
return [4 /*yield*/, sasjs
|
|
66
65
|
.deployServicePack(jsonObject, undefined, undefined, access_token, isForced)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,146 @@
|
|
|
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 (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
26
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
27
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
28
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
29
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
30
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
31
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
32
|
+
});
|
|
33
|
+
};
|
|
34
|
+
var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
35
|
+
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
|
|
36
|
+
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
37
|
+
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
38
|
+
function step(op) {
|
|
39
|
+
if (f) throw new TypeError("Generator is already executing.");
|
|
40
|
+
while (g && (g = 0, op[0] && (_ = 0)), _) try {
|
|
41
|
+
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
|
42
|
+
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
43
|
+
switch (op[0]) {
|
|
44
|
+
case 0: case 1: t = op; break;
|
|
45
|
+
case 4: _.label++; return { value: op[1], done: false };
|
|
46
|
+
case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
47
|
+
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
48
|
+
default:
|
|
49
|
+
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
50
|
+
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
51
|
+
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
52
|
+
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
53
|
+
if (t[2]) _.ops.pop();
|
|
54
|
+
_.trys.pop(); continue;
|
|
55
|
+
}
|
|
56
|
+
op = body.call(thisArg, _);
|
|
57
|
+
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
58
|
+
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
62
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
63
|
+
};
|
|
64
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
65
|
+
var path_1 = __importDefault(require("path"));
|
|
66
|
+
var os_1 = __importDefault(require("os"));
|
|
67
|
+
var utils_1 = require("@sasjs/utils");
|
|
68
|
+
var configUtils = __importStar(require("../../../utils/config"));
|
|
69
|
+
var deployToSasViyaWithServicePack_1 = require("../deployToSasViyaWithServicePack");
|
|
70
|
+
var target = new utils_1.Target({
|
|
71
|
+
name: 'test',
|
|
72
|
+
appLoc: '/Public/test/',
|
|
73
|
+
serverType: utils_1.ServerType.SasViya,
|
|
74
|
+
serverUrl: 'https://server.com',
|
|
75
|
+
contextName: 'test context'
|
|
76
|
+
});
|
|
77
|
+
var mockAuthConfig = {
|
|
78
|
+
client: 'cl13nt',
|
|
79
|
+
secret: '53cr3t',
|
|
80
|
+
access_token: 'acc355',
|
|
81
|
+
refresh_token: 'r3fr35h'
|
|
82
|
+
};
|
|
83
|
+
describe('deployToSasViyaWithServicePack', function () {
|
|
84
|
+
var jsonFilePath;
|
|
85
|
+
var deployServicePack;
|
|
86
|
+
beforeEach(function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
87
|
+
return __generator(this, function (_a) {
|
|
88
|
+
switch (_a.label) {
|
|
89
|
+
case 0:
|
|
90
|
+
jsonFilePath = path_1.default.join(os_1.default.tmpdir(), "servicepack-".concat(Date.now(), ".json"));
|
|
91
|
+
return [4 /*yield*/, (0, utils_1.createFile)(jsonFilePath, JSON.stringify({ members: [] }))];
|
|
92
|
+
case 1:
|
|
93
|
+
_a.sent();
|
|
94
|
+
deployServicePack = jest.fn().mockResolvedValue({});
|
|
95
|
+
jest
|
|
96
|
+
.spyOn(configUtils, 'getSASjs')
|
|
97
|
+
.mockImplementation(function () { return ({ deployServicePack: deployServicePack }); });
|
|
98
|
+
return [2 /*return*/];
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
}); });
|
|
102
|
+
afterEach(function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
103
|
+
return __generator(this, function (_a) {
|
|
104
|
+
switch (_a.label) {
|
|
105
|
+
case 0: return [4 /*yield*/, (0, utils_1.deleteFile)(jsonFilePath)];
|
|
106
|
+
case 1:
|
|
107
|
+
_a.sent();
|
|
108
|
+
jest.restoreAllMocks();
|
|
109
|
+
return [2 /*return*/];
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
}); });
|
|
113
|
+
it('refreshes and persists the access token via getAuthConfig instead of the non-persisting getAccessToken', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
114
|
+
var getAuthConfigSpy;
|
|
115
|
+
return __generator(this, function (_a) {
|
|
116
|
+
switch (_a.label) {
|
|
117
|
+
case 0:
|
|
118
|
+
getAuthConfigSpy = jest
|
|
119
|
+
.spyOn(configUtils, 'getAuthConfig')
|
|
120
|
+
.mockImplementation(function () { return Promise.resolve(mockAuthConfig); });
|
|
121
|
+
return [4 /*yield*/, expect((0, deployToSasViyaWithServicePack_1.deployToSasViyaWithServicePack)(jsonFilePath, target, true, false)).toResolve()];
|
|
122
|
+
case 1:
|
|
123
|
+
_a.sent();
|
|
124
|
+
expect(getAuthConfigSpy).toHaveBeenCalledWith(target);
|
|
125
|
+
expect(deployServicePack).toHaveBeenCalledWith({ members: [] }, undefined, undefined, mockAuthConfig.access_token, false);
|
|
126
|
+
return [2 /*return*/];
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
}); });
|
|
130
|
+
it('surfaces the real auth/refresh error instead of a generic "add these variables" message when credentials already exist', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
131
|
+
var realError;
|
|
132
|
+
return __generator(this, function (_a) {
|
|
133
|
+
switch (_a.label) {
|
|
134
|
+
case 0:
|
|
135
|
+
realError = new Error('invalid_grant: refresh token has already been used');
|
|
136
|
+
jest.spyOn(configUtils, 'getAuthConfig').mockImplementation(function () {
|
|
137
|
+
return Promise.reject(realError);
|
|
138
|
+
});
|
|
139
|
+
return [4 /*yield*/, expect((0, deployToSasViyaWithServicePack_1.deployToSasViyaWithServicePack)(jsonFilePath, target, true, false)).rejects.toThrow(/invalid_grant: refresh token has already been used/)];
|
|
140
|
+
case 1:
|
|
141
|
+
_a.sent();
|
|
142
|
+
return [2 /*return*/];
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
}); });
|
|
146
|
+
});
|
package/build/doxy/Doxyfile
CHANGED
|
@@ -22,7 +22,7 @@ INHERIT_DOCS = NO
|
|
|
22
22
|
INLINE_INFO = NO
|
|
23
23
|
INPUT = $(DOXY_INPUT)
|
|
24
24
|
LAYOUT_FILE = $(LAYOUT_FILE)
|
|
25
|
-
USE_MDFILE_AS_MAINPAGE =
|
|
25
|
+
USE_MDFILE_AS_MAINPAGE = $(DOXY_MAINPAGE)
|
|
26
26
|
MAX_INITIALIZER_LINES = 0
|
|
27
27
|
PROJECT_NAME = $(PROJECT_NAME)
|
|
28
28
|
PROJECT_LOGO = $(PROJECT_LOGO)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,118 @@
|
|
|
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 (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
26
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
27
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
28
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
29
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
30
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
31
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
32
|
+
});
|
|
33
|
+
};
|
|
34
|
+
var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
35
|
+
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
|
|
36
|
+
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
37
|
+
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
38
|
+
function step(op) {
|
|
39
|
+
if (f) throw new TypeError("Generator is already executing.");
|
|
40
|
+
while (g && (g = 0, op[0] && (_ = 0)), _) try {
|
|
41
|
+
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
|
42
|
+
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
43
|
+
switch (op[0]) {
|
|
44
|
+
case 0: case 1: t = op; break;
|
|
45
|
+
case 4: _.label++; return { value: op[1], done: false };
|
|
46
|
+
case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
47
|
+
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
48
|
+
default:
|
|
49
|
+
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
50
|
+
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
51
|
+
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
52
|
+
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
53
|
+
if (t[2]) _.ops.pop();
|
|
54
|
+
_.trys.pop(); continue;
|
|
55
|
+
}
|
|
56
|
+
op = body.call(thisArg, _);
|
|
57
|
+
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
58
|
+
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
62
|
+
var utils_1 = require("@sasjs/utils");
|
|
63
|
+
var configUtils = __importStar(require("../../../utils/config"));
|
|
64
|
+
var loadEnvVariablesModule = __importStar(require("../../../utils/loadEnvVariables"));
|
|
65
|
+
var test_1 = require("../../../utils/test");
|
|
66
|
+
var targetCommand_1 = require("../targetCommand");
|
|
67
|
+
var returnCode_1 = require("../returnCode");
|
|
68
|
+
describe('TargetCommand.getTargetInfo', function () {
|
|
69
|
+
beforeEach(function () {
|
|
70
|
+
process.logger = new utils_1.Logger(utils_1.LogLevel.Off);
|
|
71
|
+
jest.spyOn(process.logger, 'error');
|
|
72
|
+
});
|
|
73
|
+
it('exits with an internal error when target env variables fail to load', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
74
|
+
var processExitSpy, command;
|
|
75
|
+
return __generator(this, function (_a) {
|
|
76
|
+
switch (_a.label) {
|
|
77
|
+
case 0:
|
|
78
|
+
processExitSpy = (0, test_1.mockProcessExit)();
|
|
79
|
+
jest
|
|
80
|
+
.spyOn(loadEnvVariablesModule, 'loadTargetEnvVariables')
|
|
81
|
+
.mockImplementation(function () { return Promise.reject(new Error('env file missing')); });
|
|
82
|
+
command = new targetCommand_1.TargetCommand(['node', 'sasjs', 'job'], {
|
|
83
|
+
strict: false
|
|
84
|
+
});
|
|
85
|
+
return [4 /*yield*/, command.getTargetInfo()];
|
|
86
|
+
case 1:
|
|
87
|
+
_a.sent();
|
|
88
|
+
expect(process.logger.error).toHaveBeenCalledWith(expect.stringContaining('Error loading environment variables'), expect.any(Error));
|
|
89
|
+
expect(processExitSpy).toHaveBeenCalledWith(returnCode_1.ReturnCode.InternalError);
|
|
90
|
+
return [2 /*return*/];
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
}); });
|
|
94
|
+
it('exits with an internal error when the target cannot be found in configuration', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
95
|
+
var processExitSpy, command;
|
|
96
|
+
return __generator(this, function (_a) {
|
|
97
|
+
switch (_a.label) {
|
|
98
|
+
case 0:
|
|
99
|
+
processExitSpy = (0, test_1.mockProcessExit)();
|
|
100
|
+
jest
|
|
101
|
+
.spyOn(loadEnvVariablesModule, 'loadTargetEnvVariables')
|
|
102
|
+
.mockImplementation(function () { return Promise.resolve(); });
|
|
103
|
+
jest
|
|
104
|
+
.spyOn(configUtils, 'findTargetInConfiguration')
|
|
105
|
+
.mockImplementation(function () { return Promise.reject(new Error('target not found')); });
|
|
106
|
+
command = new targetCommand_1.TargetCommand(['node', 'sasjs', 'job'], {
|
|
107
|
+
strict: false
|
|
108
|
+
});
|
|
109
|
+
return [4 /*yield*/, command.getTargetInfo()];
|
|
110
|
+
case 1:
|
|
111
|
+
_a.sent();
|
|
112
|
+
expect(process.logger.error).toHaveBeenCalledWith('Error reading target from configuration: ', expect.any(Error));
|
|
113
|
+
expect(processExitSpy).toHaveBeenCalledWith(returnCode_1.ReturnCode.InternalError);
|
|
114
|
+
return [2 /*return*/];
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
}); });
|
|
118
|
+
});
|
package/build/utils/auth.d.ts
CHANGED
|
@@ -18,6 +18,14 @@ export declare function isAccessTokenExpiring(token: string): boolean;
|
|
|
18
18
|
* @param {string} token- token string that will be evaluated
|
|
19
19
|
*/
|
|
20
20
|
export declare function isRefreshTokenExpiring(token?: string): boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Exchanges a refresh token for a new access/refresh token pair.
|
|
23
|
+
* SAS Viya's refresh tokens are single-use and rotate on every call: the
|
|
24
|
+
* `refresh_token` returned here supersedes the one passed in, which becomes
|
|
25
|
+
* invalid immediately. Callers must persist the returned pair (see
|
|
26
|
+
* `saveTokens` in config.ts) or a later refresh attempt with the old token
|
|
27
|
+
* will be rejected by the server.
|
|
28
|
+
*/
|
|
21
29
|
export declare function refreshTokens(sasjsInstance: SASjs, clientId: string, clientSecret: string, refreshToken: string): Promise<{
|
|
22
30
|
access_token: string;
|
|
23
31
|
refresh_token: string;
|
package/build/utils/auth.js
CHANGED
|
@@ -101,6 +101,14 @@ function isRefreshTokenExpiring(token) {
|
|
|
101
101
|
return timeToLive <= 30; // 30 seconds
|
|
102
102
|
}
|
|
103
103
|
exports.isRefreshTokenExpiring = isRefreshTokenExpiring;
|
|
104
|
+
/**
|
|
105
|
+
* Exchanges a refresh token for a new access/refresh token pair.
|
|
106
|
+
* SAS Viya's refresh tokens are single-use and rotate on every call: the
|
|
107
|
+
* `refresh_token` returned here supersedes the one passed in, which becomes
|
|
108
|
+
* invalid immediately. Callers must persist the returned pair (see
|
|
109
|
+
* `saveTokens` in config.ts) or a later refresh attempt with the old token
|
|
110
|
+
* will be rejected by the server.
|
|
111
|
+
*/
|
|
104
112
|
function refreshTokens(sasjsInstance, clientId, clientSecret, refreshToken) {
|
|
105
113
|
return __awaiter(this, void 0, void 0, function () {
|
|
106
114
|
var _a, access_token, refresh_token;
|
package/build/utils/config.d.ts
CHANGED
|
@@ -61,11 +61,29 @@ export declare function getStreamConfig(target?: Target): Promise<StreamConfig>;
|
|
|
61
61
|
export declare function sanitizeAppLoc(appLoc: string): string;
|
|
62
62
|
export declare function getProjectRoot(): Promise<string>;
|
|
63
63
|
/**
|
|
64
|
-
* Gets the auth config for the specified target
|
|
64
|
+
* Gets the auth config for the specified target, refreshing the access token
|
|
65
|
+
* (via the refresh token) if it is expiring.
|
|
66
|
+
*
|
|
67
|
+
* Unlike {@link getAccessToken}, this persists any newly issued access/refresh
|
|
68
|
+
* token pair via {@link saveTokens}. That persistence is not optional: SAS Viya
|
|
69
|
+
* issues rotating, single-use refresh tokens, so once a refresh token has been
|
|
70
|
+
* used to mint a new pair, the old one is void. If the new pair isn't written
|
|
71
|
+
* back to `.env.{target}` / the global config, the *next* CLI invocation reads
|
|
72
|
+
* the now-stale refresh token from disk and fails to authenticate - which is
|
|
73
|
+
* indistinguishable, from the user's point of view, from never having
|
|
74
|
+
* refreshed at all. This is why every command that needs an authenticated
|
|
75
|
+
* request should call `getAuthConfig`, not `getAccessToken`.
|
|
65
76
|
* @param {Target} target - the target to get an access token for.
|
|
66
77
|
* @returns {AuthConfig} - an object containing an access token, refresh token, client ID and secret.
|
|
67
78
|
*/
|
|
68
79
|
export declare function getAuthConfig(target: Target): Promise<AuthConfig>;
|
|
80
|
+
/**
|
|
81
|
+
* Persists an access/refresh token pair to wherever the target's credentials
|
|
82
|
+
* live: `.env.{targetName}` for a local target, or `authConfig` in the global
|
|
83
|
+
* `~/.sasjsrc` otherwise. Called by {@link getAuthConfig} after every refresh
|
|
84
|
+
* so that a rotated (single-use) refresh token from SAS Viya is never reused -
|
|
85
|
+
* reusing it on a subsequent CLI invocation would be rejected by the server.
|
|
86
|
+
*/
|
|
69
87
|
export declare const saveTokens: (targetName: string, client: string, secret: string, access_token: string, refresh_token: string) => Promise<void>;
|
|
70
88
|
/**
|
|
71
89
|
* Gets the auth config for the specified SAS9 target.
|
|
@@ -84,6 +102,16 @@ export declare function getAuthConfigSAS9(target: Target): AuthConfigSas9;
|
|
|
84
102
|
* If a refresh token is unavailable, we will use the client ID and secret
|
|
85
103
|
* to obtain a new access token. Manual intervention is required in this case
|
|
86
104
|
* to navigate to the URL shown and type in an authorization code.
|
|
105
|
+
*
|
|
106
|
+
* IMPORTANT: unlike {@link getAuthConfig}, this does NOT persist a refreshed
|
|
107
|
+
* access/refresh token pair back to disk - the new tokens only live for the
|
|
108
|
+
* lifetime of this process. Since SAS Viya refresh tokens are single-use and
|
|
109
|
+
* rotate on every refresh, calling this from a command whose CLI invocation
|
|
110
|
+
* ends shortly after (leaving the stale refresh token on disk) will cause the
|
|
111
|
+
* *next* invocation to fail to authenticate. Only use this where that's
|
|
112
|
+
* acceptable - e.g. one-shot, same-process cleanup calls such as
|
|
113
|
+
* `removeTestServerFolder` in test support code. Any user-facing command
|
|
114
|
+
* should use `getAuthConfig` instead.
|
|
87
115
|
* @param {object} target - the target to get an access token for.
|
|
88
116
|
* @param {string} checkIfExpiring - flag that indicates whether to do an expiry check.
|
|
89
117
|
*/
|
package/build/utils/config.js
CHANGED
|
@@ -790,7 +790,18 @@ function getProjectRoot() {
|
|
|
790
790
|
}
|
|
791
791
|
exports.getProjectRoot = getProjectRoot;
|
|
792
792
|
/**
|
|
793
|
-
* Gets the auth config for the specified target
|
|
793
|
+
* Gets the auth config for the specified target, refreshing the access token
|
|
794
|
+
* (via the refresh token) if it is expiring.
|
|
795
|
+
*
|
|
796
|
+
* Unlike {@link getAccessToken}, this persists any newly issued access/refresh
|
|
797
|
+
* token pair via {@link saveTokens}. That persistence is not optional: SAS Viya
|
|
798
|
+
* issues rotating, single-use refresh tokens, so once a refresh token has been
|
|
799
|
+
* used to mint a new pair, the old one is void. If the new pair isn't written
|
|
800
|
+
* back to `.env.{target}` / the global config, the *next* CLI invocation reads
|
|
801
|
+
* the now-stale refresh token from disk and fails to authenticate - which is
|
|
802
|
+
* indistinguishable, from the user's point of view, from never having
|
|
803
|
+
* refreshed at all. This is why every command that needs an authenticated
|
|
804
|
+
* request should call `getAuthConfig`, not `getAccessToken`.
|
|
794
805
|
* @param {Target} target - the target to get an access token for.
|
|
795
806
|
* @returns {AuthConfig} - an object containing an access token, refresh token, client ID and secret.
|
|
796
807
|
*/
|
|
@@ -874,6 +885,13 @@ function getAuthConfig(target) {
|
|
|
874
885
|
});
|
|
875
886
|
}
|
|
876
887
|
exports.getAuthConfig = getAuthConfig;
|
|
888
|
+
/**
|
|
889
|
+
* Persists an access/refresh token pair to wherever the target's credentials
|
|
890
|
+
* live: `.env.{targetName}` for a local target, or `authConfig` in the global
|
|
891
|
+
* `~/.sasjsrc` otherwise. Called by {@link getAuthConfig} after every refresh
|
|
892
|
+
* so that a rotated (single-use) refresh token from SAS Viya is never reused -
|
|
893
|
+
* reusing it on a subsequent CLI invocation would be rejected by the server.
|
|
894
|
+
*/
|
|
877
895
|
var saveTokens = function (targetName, client, secret, access_token, refresh_token) { return __awaiter(void 0, void 0, void 0, function () {
|
|
878
896
|
var isLocalTarget, VERBOSE, envFileContent, envFilePath, globalConfig, target, targetJson;
|
|
879
897
|
var _a, _b, _c;
|
|
@@ -952,6 +970,16 @@ exports.getAuthConfigSAS9 = getAuthConfigSAS9;
|
|
|
952
970
|
* If a refresh token is unavailable, we will use the client ID and secret
|
|
953
971
|
* to obtain a new access token. Manual intervention is required in this case
|
|
954
972
|
* to navigate to the URL shown and type in an authorization code.
|
|
973
|
+
*
|
|
974
|
+
* IMPORTANT: unlike {@link getAuthConfig}, this does NOT persist a refreshed
|
|
975
|
+
* access/refresh token pair back to disk - the new tokens only live for the
|
|
976
|
+
* lifetime of this process. Since SAS Viya refresh tokens are single-use and
|
|
977
|
+
* rotate on every refresh, calling this from a command whose CLI invocation
|
|
978
|
+
* ends shortly after (leaving the stale refresh token on disk) will cause the
|
|
979
|
+
* *next* invocation to fail to authenticate. Only use this where that's
|
|
980
|
+
* acceptable - e.g. one-shot, same-process cleanup calls such as
|
|
981
|
+
* `removeTestServerFolder` in test support code. Any user-facing command
|
|
982
|
+
* should use `getAuthConfig` instead.
|
|
955
983
|
* @param {object} target - the target to get an access token for.
|
|
956
984
|
* @param {string} checkIfExpiring - flag that indicates whether to do an expiry check.
|
|
957
985
|
*/
|
|
@@ -52,6 +52,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
52
52
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
53
53
|
var utils_1 = require("../utils");
|
|
54
54
|
var test_1 = require("../test");
|
|
55
|
+
var axios_1 = __importDefault(require("axios"));
|
|
55
56
|
var utils_2 = require("@sasjs/utils");
|
|
56
57
|
var node_1 = __importDefault(require("@sasjs/adapter/node"));
|
|
57
58
|
var path_1 = __importDefault(require("path"));
|
|
@@ -230,6 +231,27 @@ describe('utils', function () {
|
|
|
230
231
|
}
|
|
231
232
|
});
|
|
232
233
|
}); });
|
|
234
|
+
it('should populate all default rules when an existing .gitignore file is empty', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
235
|
+
var gitIgnoreContent;
|
|
236
|
+
return __generator(this, function (_a) {
|
|
237
|
+
switch (_a.label) {
|
|
238
|
+
case 0: return [4 /*yield*/, (0, utils_2.createFile)(gitFilePath, '')];
|
|
239
|
+
case 1:
|
|
240
|
+
_a.sent();
|
|
241
|
+
return [4 /*yield*/, expect((0, utils_1.setupGitIgnore)(folderPath)).toResolve()];
|
|
242
|
+
case 2:
|
|
243
|
+
_a.sent();
|
|
244
|
+
return [4 /*yield*/, (0, utils_2.readFile)(gitFilePath)];
|
|
245
|
+
case 3:
|
|
246
|
+
gitIgnoreContent = _a.sent();
|
|
247
|
+
expect(gitIgnoreContent).toEqual('node_modules\nsasjsbuild\nsasjsresults\n.env*\n');
|
|
248
|
+
return [4 /*yield*/, (0, utils_2.deleteFile)(gitFilePath)];
|
|
249
|
+
case 4:
|
|
250
|
+
_a.sent();
|
|
251
|
+
return [2 /*return*/];
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
}); });
|
|
233
255
|
});
|
|
234
256
|
describe('chunk', function () {
|
|
235
257
|
it('should return an array with only one element that is equal to text itself if text size is less than max length', function () {
|
|
@@ -384,4 +406,173 @@ describe('utils', function () {
|
|
|
384
406
|
});
|
|
385
407
|
}); }, 600000);
|
|
386
408
|
});
|
|
409
|
+
describe('prefixAppLoc', function () {
|
|
410
|
+
it('should return null when path is falsy', function () {
|
|
411
|
+
expect((0, utils_1.prefixAppLoc)('/my/app', '')).toEqual(null);
|
|
412
|
+
});
|
|
413
|
+
it('should prepend a leading slash to appLoc if missing', function () {
|
|
414
|
+
expect((0, utils_1.prefixAppLoc)('my/app', 'program1')).toEqual('/my/app/program1');
|
|
415
|
+
});
|
|
416
|
+
it('should keep an already-absolute path unchanged', function () {
|
|
417
|
+
expect((0, utils_1.prefixAppLoc)('/my/app', '/absolute/path')).toEqual('/absolute/path');
|
|
418
|
+
});
|
|
419
|
+
it('should join an array path before prefixing each segment', function () {
|
|
420
|
+
expect((0, utils_1.prefixAppLoc)('/my/app', ['program1', 'program2'])).toEqual('/my/app/program1 /my/app/program2');
|
|
421
|
+
});
|
|
422
|
+
});
|
|
423
|
+
describe('isSASjsProject', function () {
|
|
424
|
+
it('should return true when sasjs/sasjsconfig.json exists under projectDir', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
425
|
+
var projectDir, originalProjectDir;
|
|
426
|
+
return __generator(this, function (_a) {
|
|
427
|
+
switch (_a.label) {
|
|
428
|
+
case 0:
|
|
429
|
+
projectDir = path_1.default.join(__dirname, "is-sasjs-project-".concat((0, utils_2.generateTimestamp)()));
|
|
430
|
+
originalProjectDir = process.projectDir;
|
|
431
|
+
return [4 /*yield*/, (0, utils_2.createFolder)(path_1.default.join(projectDir, 'sasjs'))];
|
|
432
|
+
case 1:
|
|
433
|
+
_a.sent();
|
|
434
|
+
return [4 /*yield*/, (0, utils_2.createFile)(path_1.default.join(projectDir, 'sasjs', 'sasjsconfig.json'), '{}')];
|
|
435
|
+
case 2:
|
|
436
|
+
_a.sent();
|
|
437
|
+
process.projectDir = projectDir;
|
|
438
|
+
return [4 /*yield*/, expect((0, utils_1.isSASjsProject)()).resolves.toEqual(true)];
|
|
439
|
+
case 3:
|
|
440
|
+
_a.sent();
|
|
441
|
+
process.projectDir = originalProjectDir;
|
|
442
|
+
return [4 /*yield*/, (0, utils_2.deleteFolder)(projectDir)];
|
|
443
|
+
case 4:
|
|
444
|
+
_a.sent();
|
|
445
|
+
return [2 /*return*/];
|
|
446
|
+
}
|
|
447
|
+
});
|
|
448
|
+
}); });
|
|
449
|
+
it('should return false when no sasjsconfig.json is found in any parent folder', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
450
|
+
var projectDir, originalProjectDir;
|
|
451
|
+
return __generator(this, function (_a) {
|
|
452
|
+
switch (_a.label) {
|
|
453
|
+
case 0:
|
|
454
|
+
projectDir = path_1.default.join(require('os').tmpdir(), "not-a-sasjs-project-".concat((0, utils_2.generateTimestamp)()));
|
|
455
|
+
originalProjectDir = process.projectDir;
|
|
456
|
+
return [4 /*yield*/, (0, utils_2.createFolder)(projectDir)];
|
|
457
|
+
case 1:
|
|
458
|
+
_a.sent();
|
|
459
|
+
process.projectDir = projectDir;
|
|
460
|
+
return [4 /*yield*/, expect((0, utils_1.isSASjsProject)()).resolves.toEqual(false)];
|
|
461
|
+
case 2:
|
|
462
|
+
_a.sent();
|
|
463
|
+
process.projectDir = originalProjectDir;
|
|
464
|
+
return [4 /*yield*/, (0, utils_2.deleteFolder)(projectDir)];
|
|
465
|
+
case 3:
|
|
466
|
+
_a.sent();
|
|
467
|
+
return [2 /*return*/];
|
|
468
|
+
}
|
|
469
|
+
});
|
|
470
|
+
}); });
|
|
471
|
+
});
|
|
472
|
+
describe('getNodeModulePath', function () {
|
|
473
|
+
it('should return the folder path of an installed module', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
474
|
+
var modulePath;
|
|
475
|
+
return __generator(this, function (_a) {
|
|
476
|
+
switch (_a.label) {
|
|
477
|
+
case 0: return [4 /*yield*/, (0, utils_1.getNodeModulePath)('@sasjs/utils')];
|
|
478
|
+
case 1:
|
|
479
|
+
modulePath = _a.sent();
|
|
480
|
+
expect(modulePath).not.toEqual('');
|
|
481
|
+
expect(modulePath.endsWith(path_1.default.join('@sasjs', 'utils'))).toEqual(true);
|
|
482
|
+
return [2 /*return*/];
|
|
483
|
+
}
|
|
484
|
+
});
|
|
485
|
+
}); });
|
|
486
|
+
it('should return an empty string when the module cannot be found', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
487
|
+
return __generator(this, function (_a) {
|
|
488
|
+
switch (_a.label) {
|
|
489
|
+
case 0: return [4 /*yield*/, expect((0, utils_1.getNodeModulePath)('sasjs-nonexistent-module-xyz')).resolves.toEqual('')];
|
|
490
|
+
case 1:
|
|
491
|
+
_a.sent();
|
|
492
|
+
return [2 /*return*/];
|
|
493
|
+
}
|
|
494
|
+
});
|
|
495
|
+
}); });
|
|
496
|
+
});
|
|
497
|
+
describe('getUniqServicesObj', function () {
|
|
498
|
+
it('should return an empty object when services is falsy', function () {
|
|
499
|
+
expect((0, utils_1.getUniqServicesObj)(undefined)).toEqual({});
|
|
500
|
+
});
|
|
501
|
+
it('should key services by their basename, keeping the first of any duplicates', function () {
|
|
502
|
+
expect((0, utils_1.getUniqServicesObj)(['a/b/service1', 'x/service2', 'y/service1'])).toEqual({
|
|
503
|
+
service1: 'a/b/service1',
|
|
504
|
+
service2: 'x/service2'
|
|
505
|
+
});
|
|
506
|
+
});
|
|
507
|
+
});
|
|
508
|
+
describe('convertToSASStatements', function () {
|
|
509
|
+
it('should convert macroVars to %let statements', function () {
|
|
510
|
+
expect((0, utils_1.convertToSASStatements)({
|
|
511
|
+
macroVars: { var1: 'val1', var2: 'val2' }
|
|
512
|
+
})).toEqual('%let var1=val1;\n%let var2=val2;\n');
|
|
513
|
+
});
|
|
514
|
+
it('should return an empty string when there are no macroVars', function () {
|
|
515
|
+
expect((0, utils_1.convertToSASStatements)({ macroVars: {} })).toEqual('');
|
|
516
|
+
});
|
|
517
|
+
});
|
|
518
|
+
describe('terminateProcess', function () {
|
|
519
|
+
it('should exit the process with the given status code', function () {
|
|
520
|
+
var mockExit = (0, test_1.mockProcessExit)();
|
|
521
|
+
(0, utils_1.terminateProcess)(2);
|
|
522
|
+
expect(mockExit).toHaveBeenCalledWith(2);
|
|
523
|
+
});
|
|
524
|
+
});
|
|
525
|
+
describe('isSasJsServerInServerMode', function () {
|
|
526
|
+
var target = new utils_2.Target({
|
|
527
|
+
name: 'test',
|
|
528
|
+
appLoc: '/Public/test/',
|
|
529
|
+
serverType: utils_2.ServerType.Sasjs,
|
|
530
|
+
serverUrl: 'https://sasjsserver.com'
|
|
531
|
+
});
|
|
532
|
+
afterEach(function () {
|
|
533
|
+
jest.restoreAllMocks();
|
|
534
|
+
});
|
|
535
|
+
it('should return true when the server reports server mode', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
536
|
+
return __generator(this, function (_a) {
|
|
537
|
+
switch (_a.label) {
|
|
538
|
+
case 0:
|
|
539
|
+
jest
|
|
540
|
+
.spyOn(axios_1.default, 'get')
|
|
541
|
+
.mockImplementation(function () { return Promise.resolve({ data: { mode: 'server' } }); });
|
|
542
|
+
return [4 /*yield*/, expect((0, utils_1.isSasJsServerInServerMode)(target)).resolves.toEqual(true)];
|
|
543
|
+
case 1:
|
|
544
|
+
_a.sent();
|
|
545
|
+
return [2 /*return*/];
|
|
546
|
+
}
|
|
547
|
+
});
|
|
548
|
+
}); });
|
|
549
|
+
it('should return false when the server does not report server mode', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
550
|
+
return __generator(this, function (_a) {
|
|
551
|
+
switch (_a.label) {
|
|
552
|
+
case 0:
|
|
553
|
+
jest
|
|
554
|
+
.spyOn(axios_1.default, 'get')
|
|
555
|
+
.mockImplementation(function () {
|
|
556
|
+
return Promise.resolve({ data: { mode: 'desktop' } });
|
|
557
|
+
});
|
|
558
|
+
return [4 /*yield*/, expect((0, utils_1.isSasJsServerInServerMode)(target)).resolves.toEqual(false)];
|
|
559
|
+
case 1:
|
|
560
|
+
_a.sent();
|
|
561
|
+
return [2 /*return*/];
|
|
562
|
+
}
|
|
563
|
+
});
|
|
564
|
+
}); });
|
|
565
|
+
it('should throw when the server info request fails', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
566
|
+
return __generator(this, function (_a) {
|
|
567
|
+
switch (_a.label) {
|
|
568
|
+
case 0:
|
|
569
|
+
jest.spyOn(axios_1.default, 'get').mockImplementation(function () { return Promise.reject(); });
|
|
570
|
+
return [4 /*yield*/, expect((0, utils_1.isSasJsServerInServerMode)(target)).rejects.toThrow("An error occurred while fetching server info from ".concat(target.serverUrl, "/SASjsApi/info"))];
|
|
571
|
+
case 1:
|
|
572
|
+
_a.sent();
|
|
573
|
+
return [2 /*return*/];
|
|
574
|
+
}
|
|
575
|
+
});
|
|
576
|
+
}); });
|
|
577
|
+
});
|
|
387
578
|
});
|
package/build/utils/utils.js
CHANGED
|
@@ -182,16 +182,23 @@ function createTemplateApp(folderPath, template) {
|
|
|
182
182
|
var _this = this;
|
|
183
183
|
return __generator(this, function (_a) {
|
|
184
184
|
return [2 /*return*/, new Promise(function (resolve, reject) { return __awaiter(_this, void 0, void 0, function () {
|
|
185
|
-
var
|
|
185
|
+
var probeFile, _a, stderr, code;
|
|
186
186
|
return __generator(this, function (_b) {
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
187
|
+
switch (_b.label) {
|
|
188
|
+
case 0:
|
|
189
|
+
probeFile = 'response.txt';
|
|
190
|
+
_a = downloadFile("https://username:password@github.com/sasjs/template_".concat(template), probeFile), stderr = _a.stderr, code = _a.code;
|
|
191
|
+
return [4 /*yield*/, (0, utils_1.deleteFile)(probeFile).catch(function () { })];
|
|
192
|
+
case 1:
|
|
193
|
+
_b.sent();
|
|
194
|
+
if (stderr.includes('404: Not Found') || code) {
|
|
195
|
+
return [2 /*return*/, reject(new Error("Template \"".concat(template, "\" is not a SASjs template")))];
|
|
196
|
+
}
|
|
197
|
+
createApp(folderPath, "https://github.com/sasjs/template_".concat(template), 'https://github.com/sasjs/docs', function (err) {
|
|
198
|
+
return reject(new Error(err));
|
|
199
|
+
});
|
|
200
|
+
return [2 /*return*/, resolve()];
|
|
190
201
|
}
|
|
191
|
-
createApp(folderPath, "https://github.com/sasjs/template_".concat(template), 'https://github.com/sasjs/docs', function (err) {
|
|
192
|
-
return reject(new Error(err));
|
|
193
|
-
});
|
|
194
|
-
return [2 /*return*/, resolve()];
|
|
195
202
|
});
|
|
196
203
|
}); })];
|
|
197
204
|
});
|
|
@@ -274,14 +281,23 @@ var loadDocsSubmodule = function (docsUrl, folderPath, zipPath) { return __await
|
|
|
274
281
|
}); };
|
|
275
282
|
function downloadFile(url, filename) {
|
|
276
283
|
if ((0, utils_1.isLinux)()) {
|
|
277
|
-
|
|
284
|
+
// -O <filename> writes to the given name; without it, wget defaults to the
|
|
285
|
+
// remote URL's basename, which silently ignores the caller's `filename` and
|
|
286
|
+
// can leave an unexpectedly-named file behind (e.g. a repo-existence probe
|
|
287
|
+
// meant to be discarded as `response.txt` instead landing as the repo name).
|
|
288
|
+
return shelljs_1.default.exec("wget ".concat(url).concat(filename ? ' -O ' + filename : ''), {
|
|
289
|
+
silent: true
|
|
290
|
+
});
|
|
278
291
|
}
|
|
279
292
|
else if ((0, utils_1.isWindows)()) {
|
|
280
293
|
// First We set TLS12 & then we invoke request to download file.
|
|
281
294
|
return shelljs_1.default.exec("powershell.exe \"[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; Invoke-WebRequest ".concat(url, " ").concat(filename ? '-O ' + filename : '', "\""), { silent: true });
|
|
282
295
|
}
|
|
283
296
|
else {
|
|
284
|
-
|
|
297
|
+
// -o <filename> writes to the given name; -O (used previously) instead saves
|
|
298
|
+
// under the remote URL's basename, ignoring `filename` - see the wget comment
|
|
299
|
+
// above for the same issue.
|
|
300
|
+
return shelljs_1.default.exec("curl ".concat(url, " -L -f").concat(filename ? ' -o ' + filename : ' -O'), { silent: true });
|
|
285
301
|
}
|
|
286
302
|
}
|
|
287
303
|
function setupNpmProject(folderName) {
|
package/package.json
CHANGED