@sasjs/cli 4.20.6 → 4.20.8

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.
Files changed (42) hide show
  1. package/build/commands/add/spec/addTarget.spec.js +5 -5
  2. package/build/commands/build/spec/build.spec.js +3 -3
  3. package/build/commands/compile/internal/spec/compileTestFile.spec.js +1 -1
  4. package/build/commands/compile/spec/compareFolders.spec.js +2 -1
  5. package/build/commands/compile/spec/compile.spec.js +7 -7
  6. package/build/commands/compile/spec/compileFile.spec.js +1 -1
  7. package/build/commands/compile/spec/copyFilesToBuildFolder.spec.js +1 -1
  8. package/build/commands/compile/spec/loadDependencies.spec.js +1 -1
  9. package/build/commands/create/spec/create.spec.js +45 -11
  10. package/build/commands/db/spec/db.spec.js +1 -1
  11. package/build/commands/deploy/spec/cbd.spec.js +1 -1
  12. package/build/commands/deploy/spec/deploy.spec.js +1 -1
  13. package/build/commands/deploy/spec/getDeployScripts.spec.js +1 -1
  14. package/build/commands/docs/generateDocs.js +2 -2
  15. package/build/commands/docs/spec/generateDocs.spec.js +41 -8
  16. package/build/commands/docs/spec/generateDot.spec.js +3 -3
  17. package/build/commands/docs/spec/initDocs.spec.js +1 -1
  18. package/build/commands/flow/internal/spec/saveToCsv.spec.js +4 -3
  19. package/build/commands/flow/internal/spec/validateParams.spec.js +2 -1
  20. package/build/commands/init/spec/init.spec.js +3 -3
  21. package/build/commands/testing/spec/testing.spec.js +1 -1
  22. package/build/commands/version/version.js +4 -9
  23. package/build/commands/web/internal/spec/updateScriptTag.spec.js +2 -1
  24. package/build/commands/web/internal/spec/updateStyleTag.spec.js +2 -1
  25. package/build/commands/web/spec/web.spec.js +1 -1
  26. package/build/utils/auth.d.ts +6 -0
  27. package/build/utils/auth.js +7 -1
  28. package/build/utils/shell.d.ts +60 -0
  29. package/build/utils/shell.js +155 -0
  30. package/build/utils/spec/auth.spec.js +13 -0
  31. package/build/utils/spec/config.spec.js +13 -13
  32. package/build/utils/spec/downloadFile.spec.js +37 -10
  33. package/build/utils/spec/file.spec.js +2 -1
  34. package/build/utils/spec/shell.spec.d.ts +1 -0
  35. package/build/utils/spec/shell.spec.js +156 -0
  36. package/build/utils/spec/utils.spec.js +5 -5
  37. package/build/utils/spec/zip.spec.js +2 -1
  38. package/build/utils/test.d.ts +10 -0
  39. package/build/utils/test.js +16 -1
  40. package/build/utils/utils.d.ts +2 -2
  41. package/build/utils/utils.js +65 -31
  42. package/package.json +8 -11
@@ -69,6 +69,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
69
69
  Object.defineProperty(exports, "__esModule", { value: true });
70
70
  var path_1 = __importDefault(require("path"));
71
71
  var utils_1 = require("@sasjs/utils");
72
+ var test_1 = require("../../../../utils/test");
72
73
  var updateScriptTag_1 = require("../updateScriptTag");
73
74
  var utils_2 = require("../../../../utils");
74
75
  describe('updateScriptTag', function () {
@@ -77,7 +78,7 @@ describe('updateScriptTag', function () {
77
78
  return __generator(this, function (_a) {
78
79
  switch (_a.label) {
79
80
  case 0:
80
- destinationPath = path_1.default.join(__dirname, "cli-test-web-updateScriptTag-".concat((0, utils_1.generateTimestamp)()));
81
+ destinationPath = path_1.default.join(__dirname, "cli-test-web-updateScriptTag-".concat((0, test_1.generateUniqueTimestamp)()));
81
82
  return [4 /*yield*/, (0, utils_1.createFolder)(destinationPath)];
82
83
  case 1:
83
84
  _a.sent();
@@ -69,6 +69,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
69
69
  Object.defineProperty(exports, "__esModule", { value: true });
70
70
  var path_1 = __importDefault(require("path"));
71
71
  var utils_1 = require("@sasjs/utils");
72
+ var test_1 = require("../../../../utils/test");
72
73
  var updateStyleTag_1 = require("../updateStyleTag");
73
74
  var utils_2 = require("../../../../utils");
74
75
  describe('updateStyleTag', function () {
@@ -77,7 +78,7 @@ describe('updateStyleTag', function () {
77
78
  return __generator(this, function (_a) {
78
79
  switch (_a.label) {
79
80
  case 0:
80
- destinationPath = path_1.default.join(__dirname, "cli-test-web-updateStyleTag-".concat((0, utils_1.generateTimestamp)()));
81
+ destinationPath = path_1.default.join(__dirname, "cli-test-web-updateStyleTag-".concat((0, test_1.generateUniqueTimestamp)()));
81
82
  return [4 /*yield*/, (0, utils_1.createFolder)(destinationPath)];
82
83
  case 1:
83
84
  _a.sent();
@@ -121,7 +121,7 @@ describe('sasjs web', function () {
121
121
  return __generator(this, function (_b) {
122
122
  switch (_b.label) {
123
123
  case 0:
124
- appName = "cli-test-web-minimal-".concat((0, utils_1.generateTimestamp)());
124
+ appName = "cli-test-web-minimal-".concat((0, test_1.generateUniqueTimestamp)());
125
125
  return [4 /*yield*/, (0, test_1.createTestMinimalApp)(__dirname, appName)];
126
126
  case 1:
127
127
  _b.sent();
@@ -1,5 +1,11 @@
1
1
  import { ServerType, Target } from '@sasjs/utils';
2
2
  import SASjs from '@sasjs/adapter/node';
3
+ /**
4
+ * The URL a user visits to obtain an authorization code.
5
+ *
6
+ * SASjs Server serves its authorization code screen at /SASjsLogon - a real
7
+ * path on its single-page app. SAS Viya answers at its own /SASLogon endpoint.
8
+ */
3
9
  export declare const getAuthUrl: (serverType: ServerType, serverUrl: string, clientId: string) => string;
4
10
  export declare function getAuthCode(authUrl: string): Promise<string>;
5
11
  /**
@@ -44,9 +44,15 @@ exports.fetchLoggedInUser = fetchLoggedInUser;
44
44
  exports.getNewAccessToken = getNewAccessToken;
45
45
  var utils_1 = require("@sasjs/utils");
46
46
  var node_1 = require("@sasjs/adapter/node");
47
+ /**
48
+ * The URL a user visits to obtain an authorization code.
49
+ *
50
+ * SASjs Server serves its authorization code screen at /SASjsLogon - a real
51
+ * path on its single-page app. SAS Viya answers at its own /SASLogon endpoint.
52
+ */
47
53
  var getAuthUrl = function (serverType, serverUrl, clientId) {
48
54
  return serverType === utils_1.ServerType.Sasjs
49
- ? "".concat(serverUrl, "/#/SASjsLogon?client_id=").concat(clientId, "&response_type=code")
55
+ ? "".concat(serverUrl, "/SASjsLogon?client_id=").concat(clientId, "&response_type=code")
50
56
  : "".concat(serverUrl, "/SASLogon/oauth/authorize?client_id=").concat(clientId, "&response_type=code");
51
57
  };
52
58
  exports.getAuthUrl = getAuthUrl;
@@ -0,0 +1,60 @@
1
+ /**
2
+ * The shell operations the CLI needs, on Node's own APIs.
3
+ *
4
+ * The CLI used shelljs for five things: run a command, copy a folder, remove a
5
+ * file or folder, list entries matching a pattern, and find an executable. Its
6
+ * current release pulls `fast-glob`, which pulls `micromatch`, which pulls
7
+ * `braces` - and `braces` has an unpatched stack-overflow advisory with no
8
+ * fixed release to move to. Nothing here needs a glob engine: the patterns are
9
+ * `*main`, `*master` and `*-main`, matched against one directory.
10
+ *
11
+ * The shapes match what the call sites already expect, so the behaviour they
12
+ * rely on - a command's stdout/stderr/exit code, and `cp -r <dir>/. <dest>`
13
+ * copying a directory's contents rather than the directory - is unchanged.
14
+ */
15
+ export interface ShellResult {
16
+ stdout: string;
17
+ stderr: string;
18
+ code: number;
19
+ }
20
+ /**
21
+ * Runs a command through the platform shell.
22
+ *
23
+ * `silent` decides whether the command's output also reaches this process's
24
+ * streams, which is how shelljs' option of the same name behaved.
25
+ */
26
+ export declare const exec: (command: string, options?: {
27
+ silent?: boolean;
28
+ }) => ShellResult;
29
+ /**
30
+ * Copies the contents of a folder into another folder, matching
31
+ * `cp -r <source>/. <destination>`.
32
+ *
33
+ * @param source - the folder to copy from, addressed as `<dir>/.` so that its
34
+ * contents land directly in the destination.
35
+ * @param destination - the folder to copy into.
36
+ */
37
+ export declare const cp: (source: string, destination: string) => Promise<void>;
38
+ /**
39
+ * Removes files and folders. `recursive` matches `rm -rf`, and without it the
40
+ * call matches `rm -f`. A target containing `*` is expanded first, because
41
+ * shelljs expanded it and the call sites rely on that.
42
+ */
43
+ export declare const rm: (targets: string[], recursive?: boolean) => void;
44
+ /**
45
+ * The entries of one directory whose name matches a pattern.
46
+ *
47
+ * Only `*` is supported, which is all the CLI's patterns use, and the result is
48
+ * addressed the same way the caller's pattern was - `./*main` answers with
49
+ * `./sasjs-react-seed-app-main`.
50
+ */
51
+ export declare const ls: (pattern: string) => string[];
52
+ /**
53
+ * The path of an executable on this machine, or null when it is not there.
54
+ *
55
+ * POSIX `which`, because that is the only question asked of it: the one caller
56
+ * wants to know whether `wget` is available, and it only asks on Linux. Answer
57
+ * `where` when something needs to ask on Windows - a branch for it now would be
58
+ * unreachable, since the caller has already ruled Windows out.
59
+ */
60
+ export declare const which: (command: string) => string | null;
@@ -0,0 +1,155 @@
1
+ "use strict";
2
+ var __values = (this && this.__values) || function(o) {
3
+ var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
4
+ if (m) return m.call(o);
5
+ if (o && typeof o.length === "number") return {
6
+ next: function () {
7
+ if (o && i >= o.length) o = void 0;
8
+ return { value: o && o[i++], done: !o };
9
+ }
10
+ };
11
+ throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
12
+ };
13
+ var __importDefault = (this && this.__importDefault) || function (mod) {
14
+ return (mod && mod.__esModule) ? mod : { "default": mod };
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.which = exports.ls = exports.rm = exports.cp = exports.exec = void 0;
18
+ var child_process_1 = require("child_process");
19
+ var fs_1 = __importDefault(require("fs"));
20
+ var path_1 = __importDefault(require("path"));
21
+ var utils_1 = require("@sasjs/utils");
22
+ /**
23
+ * Runs a command through the platform shell.
24
+ *
25
+ * `silent` decides whether the command's output also reaches this process's
26
+ * streams, which is how shelljs' option of the same name behaved.
27
+ */
28
+ var exec = function (command, options) {
29
+ var _a, _b, _c;
30
+ if (options === void 0) { options = {}; }
31
+ var result = (0, child_process_1.spawnSync)(command, {
32
+ shell: true,
33
+ encoding: 'utf8',
34
+ stdio: ['ignore', 'pipe', 'pipe'],
35
+ // shelljs allowed 20MB of output; spawnSync's 1MB default kills a chatty
36
+ // command mid-run, and a deploy script can easily be chatty.
37
+ maxBuffer: 20 * 1024 * 1024
38
+ });
39
+ var stdout = (_a = result.stdout) !== null && _a !== void 0 ? _a : '';
40
+ var stderr = (_b = result.stderr) !== null && _b !== void 0 ? _b : '';
41
+ if (!options.silent) {
42
+ if (stdout)
43
+ process.stdout.write(stdout);
44
+ if (stderr)
45
+ process.stderr.write(stderr);
46
+ }
47
+ // A killed command leaves status null and reports why on `error` - an
48
+ // exceeded buffer is ENOBUFS. Returning `status ?? 0` for that would report a
49
+ // command that never finished as a success.
50
+ if (result.error) {
51
+ return {
52
+ stdout: stdout,
53
+ stderr: [stderr, result.error.message].filter(Boolean).join('\n'),
54
+ code: 1
55
+ };
56
+ }
57
+ return { stdout: stdout, stderr: stderr, code: (_c = result.status) !== null && _c !== void 0 ? _c : 0 };
58
+ };
59
+ exports.exec = exec;
60
+ /**
61
+ * Copies the contents of a folder into another folder, matching
62
+ * `cp -r <source>/. <destination>`.
63
+ *
64
+ * @param source - the folder to copy from, addressed as `<dir>/.` so that its
65
+ * contents land directly in the destination.
66
+ * @param destination - the folder to copy into.
67
+ */
68
+ var cp = function (source, destination) {
69
+ return (0, utils_1.copy)(source, destination);
70
+ };
71
+ exports.cp = cp;
72
+ /**
73
+ * Removes files and folders. `recursive` matches `rm -rf`, and without it the
74
+ * call matches `rm -f`. A target containing `*` is expanded first, because
75
+ * shelljs expanded it and the call sites rely on that.
76
+ */
77
+ var rm = function (targets, recursive) {
78
+ var e_1, _a, e_2, _b;
79
+ if (recursive === void 0) { recursive = false; }
80
+ try {
81
+ for (var targets_1 = __values(targets), targets_1_1 = targets_1.next(); !targets_1_1.done; targets_1_1 = targets_1.next()) {
82
+ var target = targets_1_1.value;
83
+ var resolved = target.includes('*') ? (0, exports.ls)(target) : [target];
84
+ try {
85
+ for (var resolved_1 = (e_2 = void 0, __values(resolved)), resolved_1_1 = resolved_1.next(); !resolved_1_1.done; resolved_1_1 = resolved_1.next()) {
86
+ var entry = resolved_1_1.value;
87
+ fs_1.default.rmSync(entry, { recursive: recursive, force: true });
88
+ }
89
+ }
90
+ catch (e_2_1) { e_2 = { error: e_2_1 }; }
91
+ finally {
92
+ try {
93
+ if (resolved_1_1 && !resolved_1_1.done && (_b = resolved_1.return)) _b.call(resolved_1);
94
+ }
95
+ finally { if (e_2) throw e_2.error; }
96
+ }
97
+ }
98
+ }
99
+ catch (e_1_1) { e_1 = { error: e_1_1 }; }
100
+ finally {
101
+ try {
102
+ if (targets_1_1 && !targets_1_1.done && (_a = targets_1.return)) _a.call(targets_1);
103
+ }
104
+ finally { if (e_1) throw e_1.error; }
105
+ }
106
+ };
107
+ exports.rm = rm;
108
+ /**
109
+ * The entries of one directory whose name matches a pattern.
110
+ *
111
+ * Only `*` is supported, which is all the CLI's patterns use, and the result is
112
+ * addressed the same way the caller's pattern was - `./*main` answers with
113
+ * `./sasjs-react-seed-app-main`.
114
+ */
115
+ var ls = function (pattern) {
116
+ var directory = path_1.default.dirname(pattern);
117
+ var matcher = new RegExp('^' +
118
+ path_1.default
119
+ .basename(pattern)
120
+ .split('*')
121
+ .map(function (part) { return part.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); })
122
+ .join('.*') +
123
+ '$');
124
+ var entries;
125
+ try {
126
+ entries = fs_1.default.readdirSync(directory);
127
+ }
128
+ catch (_a) {
129
+ return [];
130
+ }
131
+ var matches = entries.filter(function (entry) { return matcher.test(entry); }).sort();
132
+ // `path.join` would drop the `./`, and the callers pass the result straight
133
+ // to a shell-style path, so the prefix is rebuilt rather than normalised.
134
+ return matches.map(function (entry) {
135
+ return pattern.startsWith('./') ? "./".concat(entry) : path_1.default.join(directory, entry);
136
+ });
137
+ };
138
+ exports.ls = ls;
139
+ /**
140
+ * The path of an executable on this machine, or null when it is not there.
141
+ *
142
+ * POSIX `which`, because that is the only question asked of it: the one caller
143
+ * wants to know whether `wget` is available, and it only asks on Linux. Answer
144
+ * `where` when something needs to ask on Windows - a branch for it now would be
145
+ * unreachable, since the caller has already ruled Windows out.
146
+ */
147
+ var which = function (command) {
148
+ var _a;
149
+ var result = (0, child_process_1.spawnSync)('which', [command], { encoding: 'utf8' });
150
+ if (result.status !== 0)
151
+ return null;
152
+ var first = ((_a = result.stdout) !== null && _a !== void 0 ? _a : '').split('\n')[0].trim();
153
+ return first || null;
154
+ };
155
+ exports.which = which;
@@ -86,6 +86,19 @@ var target = new utils_1.Target({
86
86
  appLoc: '/Public/test',
87
87
  contextName: 'test context'
88
88
  });
89
+ describe('getAuthUrl', function () {
90
+ it('should address the SASjs Server authorization screen as a path', function () {
91
+ expect((0, auth_1.getAuthUrl)(utils_1.ServerType.Sasjs, 'https://server.example.com', 'my-client')).toBe('https://server.example.com/SASjsLogon?client_id=my-client&response_type=code');
92
+ });
93
+ it('should build the Viya authorize URL for a SAS Viya server', function () {
94
+ expect((0, auth_1.getAuthUrl)(utils_1.ServerType.SasViya, 'https://viya.example.com', 'my-client')).toBe('https://viya.example.com/SASLogon/oauth/authorize?client_id=my-client&response_type=code');
95
+ });
96
+ it('should keep the route out of the fragment', function () {
97
+ // The screen used to live at /#/SASjsLogon. A fragment is not sent to the
98
+ // server, so the path is what makes a deep link work.
99
+ expect((0, auth_1.getAuthUrl)(utils_1.ServerType.Sasjs, 'https://server.example.com', 'my-client')).not.toContain('#');
100
+ });
101
+ });
89
102
  describe('fetchLoggedInUser', function () {
90
103
  afterEach(function () {
91
104
  jest.clearAllMocks();
@@ -456,7 +456,7 @@ describe('getAuthConfig - opaque refresh tokens', function () {
456
456
  return __generator(this, function (_a) {
457
457
  switch (_a.label) {
458
458
  case 0:
459
- projectDir = path_1.default.join(process.env.TEMP || process.env.TMP || '/tmp', "sasjs-cli-test-".concat((0, utils_1.generateTimestamp)()));
459
+ projectDir = path_1.default.join(process.env.TEMP || process.env.TMP || '/tmp', "sasjs-cli-test-".concat((0, test_1.generateUniqueTimestamp)()));
460
460
  process.projectDir = projectDir;
461
461
  return [4 /*yield*/, (0, setConstants_1.setConstants)()];
462
462
  case 1:
@@ -752,7 +752,7 @@ describe('saveToGlobalConfig', function () {
752
752
  return __generator(this, function (_b) {
753
753
  switch (_b.label) {
754
754
  case 0:
755
- appName = 'cli-tests-config-' + (0, utils_1.generateTimestamp)();
755
+ appName = 'cli-tests-config-' + (0, test_1.generateUniqueTimestamp)();
756
756
  target = (0, test_1.generateTestTarget)(appName, "/Public/app/cli-tests/".concat(appName));
757
757
  return [4 /*yield*/, (0, config_1.saveToGlobalConfig)(target, true)];
758
758
  case 1:
@@ -776,7 +776,7 @@ describe('saveToGlobalConfig', function () {
776
776
  return __generator(this, function (_b) {
777
777
  switch (_b.label) {
778
778
  case 0:
779
- appName = 'cli-tests-config-' + (0, utils_1.generateTimestamp)();
779
+ appName = 'cli-tests-config-' + (0, test_1.generateUniqueTimestamp)();
780
780
  target = (0, test_1.generateTestTarget)(appName, "/Public/app/cli-tests/".concat(appName));
781
781
  return [4 /*yield*/, (0, config_1.saveToGlobalConfig)(target, false)];
782
782
  case 1:
@@ -814,7 +814,7 @@ describe('removeFromGlobalConfig', function () {
814
814
  return __generator(this, function (_b) {
815
815
  switch (_b.label) {
816
816
  case 0:
817
- appName = 'cli-tests-config-' + (0, utils_1.generateTimestamp)();
817
+ appName = 'cli-tests-config-' + (0, test_1.generateUniqueTimestamp)();
818
818
  target = (0, test_1.generateTestTarget)(appName, "/Public/app/cli-tests/".concat(appName));
819
819
  return [4 /*yield*/, (0, config_1.saveToGlobalConfig)(target, true)];
820
820
  case 1:
@@ -842,8 +842,8 @@ describe('removeFromGlobalConfig', function () {
842
842
  return __generator(this, function (_d) {
843
843
  switch (_d.label) {
844
844
  case 0:
845
- appName1 = 'cli-tests-config-1-' + (0, utils_1.generateTimestamp)();
846
- appName2 = 'cli-tests-config-2-' + (0, utils_1.generateTimestamp)();
845
+ appName1 = 'cli-tests-config-1-' + (0, test_1.generateUniqueTimestamp)();
846
+ appName2 = 'cli-tests-config-2-' + (0, test_1.generateUniqueTimestamp)();
847
847
  target1 = (0, test_1.generateTestTarget)(appName1, "/Public/app/cli-tests/".concat(appName1));
848
848
  target2 = (0, test_1.generateTestTarget)(appName2, "/Public/app/cli-tests/".concat(appName2));
849
849
  return [4 /*yield*/, (0, config_1.saveToGlobalConfig)(target1, true)];
@@ -880,7 +880,7 @@ describe('saveToLocalConfig', function () {
880
880
  return __generator(this, function (_a) {
881
881
  switch (_a.label) {
882
882
  case 0:
883
- appName = "cli-tests-config-".concat((0, utils_1.generateTimestamp)());
883
+ appName = "cli-tests-config-".concat((0, test_1.generateUniqueTimestamp)());
884
884
  return [4 /*yield*/, (0, test_1.createTestMinimalApp)(__dirname, appName)];
885
885
  case 1:
886
886
  _a.sent();
@@ -904,7 +904,7 @@ describe('saveToLocalConfig', function () {
904
904
  return __generator(this, function (_b) {
905
905
  switch (_b.label) {
906
906
  case 0:
907
- appName = 'cli-tests-cb-' + (0, utils_1.generateTimestamp)();
907
+ appName = 'cli-tests-cb-' + (0, test_1.generateUniqueTimestamp)();
908
908
  target = (0, test_1.generateTestTarget)(appName, "/Public/app/cli-tests/".concat(appName));
909
909
  return [4 /*yield*/, (0, config_1.saveToLocalConfig)(target, true)];
910
910
  case 1:
@@ -928,7 +928,7 @@ describe('saveToLocalConfig', function () {
928
928
  return __generator(this, function (_b) {
929
929
  switch (_b.label) {
930
930
  case 0:
931
- appName = 'cli-tests-cb-' + (0, utils_1.generateTimestamp)();
931
+ appName = 'cli-tests-cb-' + (0, test_1.generateUniqueTimestamp)();
932
932
  target = (0, test_1.generateTestTarget)(appName, "/Public/app/cli-tests/".concat(appName));
933
933
  return [4 /*yield*/, (0, config_1.saveToLocalConfig)(target, false)];
934
934
  case 1:
@@ -953,7 +953,7 @@ describe('removeFromLocalConfig', function () {
953
953
  return __generator(this, function (_a) {
954
954
  switch (_a.label) {
955
955
  case 0:
956
- appName = "cli-tests-config-".concat((0, utils_1.generateTimestamp)());
956
+ appName = "cli-tests-config-".concat((0, test_1.generateUniqueTimestamp)());
957
957
  return [4 /*yield*/, (0, test_1.createTestMinimalApp)(__dirname, appName)];
958
958
  case 1:
959
959
  _a.sent();
@@ -977,7 +977,7 @@ describe('removeFromLocalConfig', function () {
977
977
  return __generator(this, function (_b) {
978
978
  switch (_b.label) {
979
979
  case 0:
980
- appName = 'cli-tests-config-' + (0, utils_1.generateTimestamp)();
980
+ appName = 'cli-tests-config-' + (0, test_1.generateUniqueTimestamp)();
981
981
  target = (0, test_1.generateTestTarget)(appName, "/Public/app/cli-tests/".concat(appName));
982
982
  return [4 /*yield*/, (0, config_1.saveToLocalConfig)(target, true)];
983
983
  case 1:
@@ -1005,8 +1005,8 @@ describe('removeFromLocalConfig', function () {
1005
1005
  return __generator(this, function (_d) {
1006
1006
  switch (_d.label) {
1007
1007
  case 0:
1008
- appName1 = 'cli-tests-config-1-' + (0, utils_1.generateTimestamp)();
1009
- appName2 = 'cli-tests-config-2-' + (0, utils_1.generateTimestamp)();
1008
+ appName1 = 'cli-tests-config-1-' + (0, test_1.generateUniqueTimestamp)();
1009
+ appName2 = 'cli-tests-config-2-' + (0, test_1.generateUniqueTimestamp)();
1010
1010
  target1 = (0, test_1.generateTestTarget)(appName1, "/Public/app/cli-tests/".concat(appName1));
1011
1011
  target2 = (0, test_1.generateTestTarget)(appName2, "/Public/app/cli-tests/".concat(appName2));
1012
1012
  return [4 /*yield*/, (0, config_1.saveToLocalConfig)(target1, true)];
@@ -10,21 +10,48 @@ var __assign = (this && this.__assign) || function () {
10
10
  };
11
11
  return __assign.apply(this, arguments);
12
12
  };
13
- var __importDefault = (this && this.__importDefault) || function (mod) {
14
- return (mod && mod.__esModule) ? mod : { "default": mod };
15
- };
13
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
14
+ if (k2 === undefined) k2 = k;
15
+ var desc = Object.getOwnPropertyDescriptor(m, k);
16
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
17
+ desc = { enumerable: true, get: function() { return m[k]; } };
18
+ }
19
+ Object.defineProperty(o, k2, desc);
20
+ }) : (function(o, m, k, k2) {
21
+ if (k2 === undefined) k2 = k;
22
+ o[k2] = m[k];
23
+ }));
24
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
25
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
26
+ }) : function(o, v) {
27
+ o["default"] = v;
28
+ });
29
+ var __importStar = (this && this.__importStar) || (function () {
30
+ var ownKeys = function(o) {
31
+ ownKeys = Object.getOwnPropertyNames || function (o) {
32
+ var ar = [];
33
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
34
+ return ar;
35
+ };
36
+ return ownKeys(o);
37
+ };
38
+ return function (mod) {
39
+ if (mod && mod.__esModule) return mod;
40
+ var result = {};
41
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
42
+ __setModuleDefault(result, mod);
43
+ return result;
44
+ };
45
+ })();
16
46
  Object.defineProperty(exports, "__esModule", { value: true });
17
- var shelljs_1 = __importDefault(require("shelljs"));
47
+ var shell = __importStar(require("../shell"));
18
48
  var utils_1 = require("@sasjs/utils");
19
49
  var utils_2 = require("../utils");
20
- jest.mock('shelljs', function () { return ({
21
- exec: jest.fn(function () { return ({ stdout: '', stderr: '', code: 0 }); }),
22
- which: jest.fn()
23
- }); });
50
+ jest.mock('../shell', function () { return (__assign(__assign({}, jest.requireActual('../shell')), { exec: jest.fn(function () { return ({ stdout: '', stderr: '', code: 0 }); }), which: jest.fn() })); });
24
51
  jest.mock('@sasjs/utils', function () { return (__assign(__assign({}, jest.requireActual('@sasjs/utils')), { isLinux: jest.fn(), isWindows: jest.fn() })); });
25
52
  describe('downloadFile', function () {
26
- var exec = shelljs_1.default.exec;
27
- var which = shelljs_1.default.which;
53
+ var exec = shell.exec;
54
+ var which = shell.which;
28
55
  var linux = utils_1.isLinux;
29
56
  var windows = utils_1.isWindows;
30
57
  beforeEach(function () {
@@ -41,9 +41,10 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
41
41
  Object.defineProperty(exports, "__esModule", { value: true });
42
42
  var path_1 = __importDefault(require("path"));
43
43
  var utils_1 = require("@sasjs/utils");
44
+ var test_1 = require("../test");
44
45
  describe('file utility', function () {
45
46
  describe('createFile', function () {
46
- var filename = "test-create-file-".concat((0, utils_1.generateTimestamp)(), ".txt");
47
+ var filename = "test-create-file-".concat((0, test_1.generateUniqueTimestamp)(), ".txt");
47
48
  it('should create a file', function () { return __awaiter(void 0, void 0, void 0, function () {
48
49
  var filePath, content;
49
50
  return __generator(this, function (_a) {
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,156 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ var fs_1 = __importDefault(require("fs"));
7
+ var os_1 = __importDefault(require("os"));
8
+ var path_1 = __importDefault(require("path"));
9
+ var shell_1 = require("../shell");
10
+ /**
11
+ * The shell wrapper, directly.
12
+ *
13
+ * Every other suite replaces this module with a mock, so nothing exercised it -
14
+ * which is how the maxBuffer mapping shipped reporting a killed command as a
15
+ * success. These cover the behaviours the call sites rely on: a command's
16
+ * stdout/stderr/exit code, `ls` matching a suffix pattern, `rm` expanding one,
17
+ * and `cp` copying a directory's contents rather than the directory.
18
+ */
19
+ describe('shell', function () {
20
+ var root;
21
+ beforeEach(function () {
22
+ root = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'sasjs-shell-spec-'));
23
+ });
24
+ afterEach(function () {
25
+ fs_1.default.rmSync(root, { recursive: true, force: true });
26
+ });
27
+ describe('exec', function () {
28
+ it('should answer with stdout and a zero code for a command that succeeds', function () {
29
+ var result = (0, shell_1.exec)("node -e \"process.stdout.write('hello')\"", {
30
+ silent: true
31
+ });
32
+ expect(result.stdout).toEqual('hello');
33
+ expect(result.stderr).toEqual('');
34
+ expect(result.code).toEqual(0);
35
+ });
36
+ it('should answer with the exit code and stderr for a command that fails', function () {
37
+ var result = (0, shell_1.exec)("node -e \"process.stderr.write('boom'); process.exit(3)\"", { silent: true });
38
+ expect(result.code).toEqual(3);
39
+ expect(result.stderr).toContain('boom');
40
+ });
41
+ it('should report a command killed by the output ceiling as a failure', function () {
42
+ // A command that exceeds maxBuffer is killed, and spawnSync reports that
43
+ // with status null and the reason on `error`. Mapping `status ?? 0` would
44
+ // call it a success, which is what a chatty deploy script would hit.
45
+ var result = (0, shell_1.exec)("node -e \"process.stdout.write('x'.repeat(25 * 1024 * 1024))\"", { silent: true });
46
+ expect(result.code).not.toEqual(0);
47
+ expect(result.stderr).not.toEqual('');
48
+ });
49
+ it('should keep a command that is merely chatty', function () {
50
+ // 3MB is over spawnSync's 1MB default and under the ceiling this module
51
+ // sets, so the command must finish rather than be killed.
52
+ var result = (0, shell_1.exec)("node -e \"process.stdout.write('x'.repeat(3 * 1024 * 1024))\"", { silent: true });
53
+ expect(result.code).toEqual(0);
54
+ expect(result.stdout.length).toEqual(3 * 1024 * 1024);
55
+ });
56
+ });
57
+ describe('ls', function () {
58
+ beforeEach(function () {
59
+ fs_1.default.mkdirSync(path_1.default.join(root, 'sasjs-react-seed-app-main'));
60
+ fs_1.default.mkdirSync(path_1.default.join(root, 'docs-main'));
61
+ fs_1.default.mkdirSync(path_1.default.join(root, 'unrelated'));
62
+ fs_1.default.writeFileSync(path_1.default.join(root, 'main.zip'), '');
63
+ });
64
+ it('should match entries by a suffix pattern', function () {
65
+ expect((0, shell_1.ls)(path_1.default.join(root, '*main')).sort()).toEqual([
66
+ path_1.default.join(root, 'docs-main'),
67
+ path_1.default.join(root, 'sasjs-react-seed-app-main')
68
+ ]);
69
+ });
70
+ it('should match a pattern that narrows the suffix', function () {
71
+ expect((0, shell_1.ls)(path_1.default.join(root, '*-main'))).toEqual([
72
+ path_1.default.join(root, 'docs-main'),
73
+ path_1.default.join(root, 'sasjs-react-seed-app-main')
74
+ ]);
75
+ });
76
+ it('should keep the ./ prefix the caller passed', function () {
77
+ // createApp passes the result straight to a shell-style path, so the
78
+ // prefix survives rather than being normalised away. The pattern is
79
+ // relative, so it resolves against the working directory.
80
+ var cwd = process.cwd();
81
+ try {
82
+ process.chdir(root);
83
+ var matches = (0, shell_1.ls)('./*main');
84
+ expect(matches.sort()).toEqual([
85
+ './docs-main',
86
+ './sasjs-react-seed-app-main'
87
+ ]);
88
+ }
89
+ finally {
90
+ process.chdir(cwd);
91
+ }
92
+ });
93
+ it('should answer with nothing when the directory does not exist', function () {
94
+ expect((0, shell_1.ls)(path_1.default.join(root, 'no-such-dir', '*main'))).toEqual([]);
95
+ });
96
+ it('should answer with nothing when nothing matches', function () {
97
+ expect((0, shell_1.ls)(path_1.default.join(root, '*nothing-like-this'))).toEqual([]);
98
+ });
99
+ });
100
+ describe('rm', function () {
101
+ it('should expand a pattern before removing', function () {
102
+ fs_1.default.mkdirSync(path_1.default.join(root, 'sasjs-react-seed-app-main'));
103
+ fs_1.default.mkdirSync(path_1.default.join(root, 'docs-main'));
104
+ fs_1.default.mkdirSync(path_1.default.join(root, 'keep-me'));
105
+ (0, shell_1.rm)([path_1.default.join(root, '*main')], true);
106
+ expect(fs_1.default.existsSync(path_1.default.join(root, 'sasjs-react-seed-app-main'))).toBe(false);
107
+ expect(fs_1.default.existsSync(path_1.default.join(root, 'docs-main'))).toBe(false);
108
+ expect(fs_1.default.existsSync(path_1.default.join(root, 'keep-me'))).toBe(true);
109
+ });
110
+ it('should remove a directory and its contents when recursive', function () {
111
+ var target = path_1.default.join(root, 'nested');
112
+ fs_1.default.mkdirSync(path_1.default.join(target, 'deep'), { recursive: true });
113
+ fs_1.default.writeFileSync(path_1.default.join(target, 'deep', 'file.txt'), 'x');
114
+ (0, shell_1.rm)([target], true);
115
+ expect(fs_1.default.existsSync(target)).toBe(false);
116
+ });
117
+ it('should remove a single file', function () {
118
+ var file = path_1.default.join(root, 'main.zip');
119
+ fs_1.default.writeFileSync(file, '');
120
+ (0, shell_1.rm)([file]);
121
+ expect(fs_1.default.existsSync(file)).toBe(false);
122
+ });
123
+ it('should not throw for something that is not there', function () {
124
+ expect(function () { return (0, shell_1.rm)([path_1.default.join(root, 'missing')], true); }).not.toThrow();
125
+ });
126
+ });
127
+ describe('cp', function () {
128
+ it("should copy a directory's contents into the destination", function () {
129
+ var source = path_1.default.join(root, 'sasjs-react-seed-app-main');
130
+ fs_1.default.mkdirSync(path_1.default.join(source, 'nested'), { recursive: true });
131
+ fs_1.default.writeFileSync(path_1.default.join(source, 'package.json'), '{}');
132
+ fs_1.default.writeFileSync(path_1.default.join(source, 'nested', 'file.txt'), 'x');
133
+ var destination = path_1.default.join(root, 'my-new-app');
134
+ fs_1.default.mkdirSync(destination);
135
+ return (0, shell_1.cp)("".concat(source, "/."), destination).then(function () {
136
+ // The contents land in the destination, not a folder named after the
137
+ // source - the difference between `cp -r <dir>/. <dest>` and
138
+ // `cp -r <dir> <dest>`, which the create flow depends on.
139
+ expect(fs_1.default.existsSync(path_1.default.join(destination, 'package.json'))).toBe(true);
140
+ expect(fs_1.default.existsSync(path_1.default.join(destination, 'nested', 'file.txt'))).toBe(true);
141
+ expect(fs_1.default.existsSync(path_1.default.join(destination, 'sasjs-react-seed-app-main'))).toBe(false);
142
+ });
143
+ });
144
+ });
145
+ // `which` answers the POSIX question its one caller asks, and that caller
146
+ // only asks on Linux, so the assertions are Linux-only.
147
+ var describePosix = process.platform === 'win32' ? describe.skip : describe;
148
+ describePosix('which', function () {
149
+ it('should answer with the path of an executable that is there', function () {
150
+ expect((0, shell_1.which)('sh')).toMatch(/sh$/);
151
+ });
152
+ it('should answer with null for one that is not', function () {
153
+ expect((0, shell_1.which)('definitely-not-a-real-executable-xyz')).toBeNull();
154
+ });
155
+ });
156
+ });