@sasjs/cli 4.20.2 → 4.20.4

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/README.md CHANGED
@@ -53,6 +53,10 @@ Running `npm i -g @sasjs/cli@latest` does not always upgrade properly. This is d
53
53
 
54
54
  If you are running NPM in version 6 or below, you can try running `npm update -g @sasjs/cli` instead, or - the sledgehammer approach - delete the files from the NPM folder and then do a fresh install.
55
55
 
56
+ ## Contributing
57
+
58
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for local setup.
59
+
56
60
  ## Star Gazing
57
61
 
58
62
  If you find this library useful, please leave a star and help us grow our star graph!
@@ -208,7 +208,7 @@ function runTest(target_1) {
208
208
  printCodeExample = function () {
209
209
  var _a;
210
210
  if (!isCodeExamplePrinted) {
211
- (_a = process.logger) === null || _a === void 0 ? void 0 : _a.info("Code example to provide output:\n data work.test_results;\n test_description=\"some description\";\n test_result=\"PASS\";\n output;\n run;\n %webout(OPEN)\n %webout(OBJ, test_results)\n %webout(CLOSE)\n");
211
+ (_a = process.logger) === null || _a === void 0 ? void 0 : _a.info("Code example to provide output:\n data work.test_results;\n length test_result $5 test_description $256 test_comments $256;\n test_description=\"some description\";\n test_result=\"PASS\";\n output;\n run;\n %webout(OPEN)\n %webout(OBJ, test_results)\n %webout(CLOSE)\n");
212
212
  isCodeExamplePrinted = true;
213
213
  }
214
214
  };
@@ -41,7 +41,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
41
41
  Object.defineProperty(exports, "__esModule", { value: true });
42
42
  exports.compressAndSave = void 0;
43
43
  var path_1 = __importDefault(require("path"));
44
- var adm_zip_1 = __importDefault(require("adm-zip"));
44
+ var fs_1 = __importDefault(require("fs"));
45
+ var yazl_1 = __importDefault(require("yazl"));
45
46
  /**
46
47
  * Creates a zip file.
47
48
  * Having single JSON file in it.
@@ -53,11 +54,15 @@ var compressAndSave = function (saveTo, contents) { return __awaiter(void 0, voi
53
54
  return __generator(this, function (_a) {
54
55
  switch (_a.label) {
55
56
  case 0:
56
- zip = new adm_zip_1.default();
57
+ zip = new yazl_1.default.ZipFile();
57
58
  filenameInZip = path_1.default.basename(saveTo, path_1.default.extname(saveTo));
58
59
  // add file directly
59
- zip.addFile(filenameInZip, Buffer.from(contents, 'utf8'), 'entry comment goes here');
60
- return [4 /*yield*/, zip.writeZipPromise(saveTo, { overwrite: true })];
60
+ zip.addBuffer(Buffer.from(contents, 'utf8'), filenameInZip);
61
+ return [4 /*yield*/, new Promise(function (resolve, reject) {
62
+ zip.outputStream.on('error', reject);
63
+ zip.outputStream.pipe(fs_1.default.createWriteStream(saveTo)).on('close', resolve);
64
+ zip.end();
65
+ })];
61
66
  case 1:
62
67
  _a.sent();
63
68
  return [2 /*return*/];
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,282 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __generator = (this && this.__generator) || function (thisArg, body) {
12
+ var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
13
+ return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
14
+ function verb(n) { return function (v) { return step([n, v]); }; }
15
+ function step(op) {
16
+ if (f) throw new TypeError("Generator is already executing.");
17
+ while (g && (g = 0, op[0] && (_ = 0)), _) try {
18
+ 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;
19
+ if (y = 0, t) op = [op[0] & 2, t.value];
20
+ switch (op[0]) {
21
+ case 0: case 1: t = op; break;
22
+ case 4: _.label++; return { value: op[1], done: false };
23
+ case 5: _.label++; y = op[1]; op = [0]; continue;
24
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
25
+ default:
26
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
27
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
28
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
29
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
30
+ if (t[2]) _.ops.pop();
31
+ _.trys.pop(); continue;
32
+ }
33
+ op = body.call(thisArg, _);
34
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
35
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
36
+ }
37
+ };
38
+ var __importDefault = (this && this.__importDefault) || function (mod) {
39
+ return (mod && mod.__esModule) ? mod : { "default": mod };
40
+ };
41
+ Object.defineProperty(exports, "__esModule", { value: true });
42
+ var fs_1 = __importDefault(require("fs"));
43
+ var path_1 = __importDefault(require("path"));
44
+ var yazl_1 = __importDefault(require("yazl"));
45
+ var utils_1 = require("@sasjs/utils");
46
+ var zip_1 = require("../zip");
47
+ var utils_2 = require("@sasjs/utils");
48
+ var makeZip = function (entries, zipPath) {
49
+ return new Promise(function (resolve, reject) {
50
+ var zip = new yazl_1.default.ZipFile();
51
+ entries.forEach(function (e) {
52
+ return zip.addBuffer(Buffer.from(e.content, 'utf8'), e.name, { mode: e.mode });
53
+ });
54
+ zip.outputStream.pipe(fs_1.default.createWriteStream(zipPath)).on('close', resolve);
55
+ zip.outputStream.on('error', reject);
56
+ zip.end();
57
+ });
58
+ };
59
+ /**
60
+ * Rewrites the versionMadeBy host platform byte (high byte of the central
61
+ * directory field) of every entry, simulating an archive created on a
62
+ * different host (e.g. 0 = MS-DOS/Windows). Used to prove that mode bits
63
+ * from non-unix hosts are ignored.
64
+ */
65
+ var setEntryPlatform = function (zipPath, platform) {
66
+ var buffer = fs_1.default.readFileSync(zipPath);
67
+ var eocd = buffer.lastIndexOf(Buffer.from([0x50, 0x4b, 0x05, 0x06]));
68
+ if (eocd < 0)
69
+ throw new Error('end of central directory record not found');
70
+ var cdSize = buffer.readUInt32LE(eocd + 12);
71
+ var cdOffset = buffer.readUInt32LE(eocd + 16);
72
+ var p = cdOffset;
73
+ var end = cdOffset + cdSize;
74
+ while (p < end) {
75
+ if (buffer.readUInt32LE(p) !== 0x02014b50)
76
+ break; // central file header
77
+ var versionMadeBy = buffer.readUInt16LE(p + 4);
78
+ buffer.writeUInt16LE((versionMadeBy & 0xff) | (platform << 8), p + 4);
79
+ var nlen = buffer.readUInt16LE(p + 28);
80
+ var elen = buffer.readUInt16LE(p + 30);
81
+ var clen = buffer.readUInt16LE(p + 32);
82
+ p += 46 + nlen + elen + clen;
83
+ }
84
+ fs_1.default.writeFileSync(zipPath, buffer);
85
+ };
86
+ describe('zip', function () {
87
+ var tmpRoot = path_1.default.join(__dirname, "zip-spec-".concat((0, utils_2.generateTimestamp)()));
88
+ var destDir = path_1.default.join(tmpRoot, 'dest');
89
+ beforeAll(function () { return __awaiter(void 0, void 0, void 0, function () {
90
+ return __generator(this, function (_a) {
91
+ fs_1.default.mkdirSync(destDir, { recursive: true });
92
+ return [2 /*return*/];
93
+ });
94
+ }); });
95
+ afterAll(function () { return __awaiter(void 0, void 0, void 0, function () {
96
+ return __generator(this, function (_a) {
97
+ switch (_a.label) {
98
+ case 0: return [4 /*yield*/, (0, utils_2.deleteFolder)(tmpRoot).catch(function () { })];
99
+ case 1:
100
+ _a.sent();
101
+ return [2 /*return*/];
102
+ }
103
+ });
104
+ }); });
105
+ describe('extractZip', function () {
106
+ it('should extract nested files and create implicit directories', function () { return __awaiter(void 0, void 0, void 0, function () {
107
+ var zipPath, nested, top;
108
+ return __generator(this, function (_a) {
109
+ switch (_a.label) {
110
+ case 0:
111
+ zipPath = path_1.default.join(tmpRoot, 'app.zip');
112
+ return [4 /*yield*/, makeZip([
113
+ { name: 'app-main/a/b/file.txt', content: 'nested content' },
114
+ { name: 'app-main/top.txt', content: 'top content' }
115
+ ], zipPath)];
116
+ case 1:
117
+ _a.sent();
118
+ return [4 /*yield*/, (0, zip_1.extractZip)(zipPath, destDir, true)];
119
+ case 2:
120
+ _a.sent();
121
+ return [4 /*yield*/, (0, utils_2.readFile)(path_1.default.join(destDir, 'app-main', 'a', 'b', 'file.txt'))];
122
+ case 3:
123
+ nested = _a.sent();
124
+ expect(nested).toEqual('nested content');
125
+ return [4 /*yield*/, (0, utils_2.readFile)(path_1.default.join(destDir, 'app-main', 'top.txt'))];
126
+ case 4:
127
+ top = _a.sent();
128
+ expect(top).toEqual('top content');
129
+ return [2 /*return*/];
130
+ }
131
+ });
132
+ }); });
133
+ it('should create explicit directory entries', function () { return __awaiter(void 0, void 0, void 0, function () {
134
+ var zipPath;
135
+ return __generator(this, function (_a) {
136
+ switch (_a.label) {
137
+ case 0:
138
+ zipPath = path_1.default.join(tmpRoot, 'dirs.zip');
139
+ return [4 /*yield*/, makeZip([{ name: 'app-main/public/docs/.gitkeep', content: '' }], zipPath)];
140
+ case 1:
141
+ _a.sent();
142
+ return [4 /*yield*/, (0, zip_1.extractZip)(zipPath, destDir, true)];
143
+ case 2:
144
+ _a.sent();
145
+ expect(fs_1.default.existsSync(path_1.default.join(destDir, 'app-main', 'public', 'docs'))).toBeTrue();
146
+ return [2 /*return*/];
147
+ }
148
+ });
149
+ }); });
150
+ it('should preserve unix mode bits', function () { return __awaiter(void 0, void 0, void 0, function () {
151
+ var zipPath, hookMode;
152
+ return __generator(this, function (_a) {
153
+ switch (_a.label) {
154
+ case 0:
155
+ if ((0, utils_1.isWindows)())
156
+ return [2 /*return*/]; // chmod exec bits are a no-op on Windows
157
+ zipPath = path_1.default.join(tmpRoot, 'modes.zip');
158
+ return [4 /*yield*/, makeZip([
159
+ { name: 'app-main/hook', content: '#!/bin/bash\n', mode: 493 },
160
+ { name: 'app-main/plain.txt', content: 'plain', mode: 420 }
161
+ ], zipPath)];
162
+ case 1:
163
+ _a.sent();
164
+ return [4 /*yield*/, (0, zip_1.extractZip)(zipPath, destDir, true)];
165
+ case 2:
166
+ _a.sent();
167
+ hookMode = fs_1.default.statSync(path_1.default.join(destDir, 'app-main', 'hook')).mode;
168
+ expect(hookMode & 73).not.toEqual(0);
169
+ return [2 /*return*/];
170
+ }
171
+ });
172
+ }); });
173
+ it('should not apply mode bits from non-unix (e.g. Windows) archives', function () { return __awaiter(void 0, void 0, void 0, function () {
174
+ var zipPath, mode;
175
+ return __generator(this, function (_a) {
176
+ switch (_a.label) {
177
+ case 0:
178
+ if ((0, utils_1.isWindows)())
179
+ return [2 /*return*/]; // chmod exec bits are a no-op on Windows
180
+ zipPath = path_1.default.join(tmpRoot, 'winmodes.zip');
181
+ return [4 /*yield*/, makeZip([{ name: 'app-main/binary', content: 'MZ', mode: 493 }], zipPath)
182
+ // Simulate a Windows-created archive: same externalFileAttributes
183
+ // (exec bits set by yazl), but versionMadeBy platform 0 (MS-DOS).
184
+ ];
185
+ case 1:
186
+ _a.sent();
187
+ // Simulate a Windows-created archive: same externalFileAttributes
188
+ // (exec bits set by yazl), but versionMadeBy platform 0 (MS-DOS).
189
+ setEntryPlatform(zipPath, 0);
190
+ return [4 /*yield*/, (0, zip_1.extractZip)(zipPath, destDir, true)
191
+ // The entry's externalFileAttributes high bits are not a unix mode
192
+ // on non-unix platforms - applying them would chmod the file 755.
193
+ ];
194
+ case 2:
195
+ _a.sent();
196
+ mode = fs_1.default.statSync(path_1.default.join(destDir, 'app-main', 'binary')).mode;
197
+ expect(mode & 73).toEqual(0);
198
+ return [2 /*return*/];
199
+ }
200
+ });
201
+ }); });
202
+ it('should not write through a symlink at the destination', function () { return __awaiter(void 0, void 0, void 0, function () {
203
+ var zipPath, victimPath, victim;
204
+ return __generator(this, function (_a) {
205
+ switch (_a.label) {
206
+ case 0:
207
+ if ((0, utils_1.isWindows)())
208
+ return [2 /*return*/]; // symlink creation needs elevated privileges
209
+ zipPath = path_1.default.join(tmpRoot, 'evil.zip');
210
+ return [4 /*yield*/, makeZip([{ name: 'app-main/target.txt', content: 'EVIL' }], zipPath)];
211
+ case 1:
212
+ _a.sent();
213
+ victimPath = path_1.default.join(tmpRoot, 'victim.txt');
214
+ return [4 /*yield*/, (0, utils_2.createFile)(victimPath, 'ORIGINAL')
215
+ // plant a symlink where the entry wants to write
216
+ ];
217
+ case 2:
218
+ _a.sent();
219
+ // plant a symlink where the entry wants to write
220
+ fs_1.default.mkdirSync(path_1.default.join(destDir, 'app-main'), { recursive: true });
221
+ fs_1.default.symlinkSync(victimPath, path_1.default.join(destDir, 'app-main', 'target.txt'));
222
+ return [4 /*yield*/, expect((0, zip_1.extractZip)(zipPath, destDir, true)).rejects.toThrow(/symbolic link/)];
223
+ case 3:
224
+ _a.sent();
225
+ return [4 /*yield*/, (0, utils_2.readFile)(victimPath)];
226
+ case 4:
227
+ victim = _a.sent();
228
+ expect(victim).toEqual('ORIGINAL');
229
+ return [2 /*return*/];
230
+ }
231
+ });
232
+ }); });
233
+ it('should not extract into a symlinked parent directory', function () { return __awaiter(void 0, void 0, void 0, function () {
234
+ var zipPath, realDir, linkDir;
235
+ return __generator(this, function (_a) {
236
+ switch (_a.label) {
237
+ case 0:
238
+ if ((0, utils_1.isWindows)())
239
+ return [2 /*return*/]; // symlink creation needs elevated privileges
240
+ zipPath = path_1.default.join(tmpRoot, 'evilparent.zip');
241
+ return [4 /*yield*/, makeZip([{ name: 'app-main/x.txt', content: 'x' }], zipPath)];
242
+ case 1:
243
+ _a.sent();
244
+ realDir = path_1.default.join(tmpRoot, 'realdir');
245
+ fs_1.default.mkdirSync(realDir, { recursive: true });
246
+ linkDir = path_1.default.join(tmpRoot, 'linkdir');
247
+ fs_1.default.symlinkSync(realDir, linkDir);
248
+ return [4 /*yield*/, expect((0, zip_1.extractZip)(zipPath, linkDir, true)).rejects.toThrow(/symbolic link/)];
249
+ case 2:
250
+ _a.sent();
251
+ expect(fs_1.default.readdirSync(realDir).length).toEqual(0);
252
+ return [2 /*return*/];
253
+ }
254
+ });
255
+ }); });
256
+ it('should skip existing files when overwrite is false', function () { return __awaiter(void 0, void 0, void 0, function () {
257
+ var zipPath, existingPath, content;
258
+ return __generator(this, function (_a) {
259
+ switch (_a.label) {
260
+ case 0:
261
+ zipPath = path_1.default.join(tmpRoot, 'skip.zip');
262
+ return [4 /*yield*/, makeZip([{ name: 'app-main/skipme.txt', content: 'NEW' }], zipPath)];
263
+ case 1:
264
+ _a.sent();
265
+ existingPath = path_1.default.join(destDir, 'app-main', 'skipme.txt');
266
+ fs_1.default.mkdirSync(path_1.default.join(destDir, 'app-main'), { recursive: true });
267
+ return [4 /*yield*/, (0, utils_2.createFile)(existingPath, 'EXISTING')];
268
+ case 2:
269
+ _a.sent();
270
+ return [4 /*yield*/, (0, zip_1.extractZip)(zipPath, destDir, false)];
271
+ case 3:
272
+ _a.sent();
273
+ return [4 /*yield*/, (0, utils_2.readFile)(existingPath)];
274
+ case 4:
275
+ content = _a.sent();
276
+ expect(content).toEqual('EXISTING');
277
+ return [2 /*return*/];
278
+ }
279
+ });
280
+ }); });
281
+ });
282
+ });
@@ -117,7 +117,7 @@ var axios_1 = __importDefault(require("axios"));
117
117
  var utils_1 = require("@sasjs/utils");
118
118
  var displayResult_1 = require("./displayResult");
119
119
  var dotenv_1 = __importDefault(require("dotenv"));
120
- var adm_zip_1 = __importDefault(require("adm-zip"));
120
+ var zip_1 = require("./zip");
121
121
  var config_1 = require("./config");
122
122
  function inExistingProject(folderPath) {
123
123
  return __awaiter(this, void 0, void 0, function () {
@@ -151,10 +151,14 @@ function createReactApp(folderPath) {
151
151
  return __generator(this, function (_a) {
152
152
  return [2 /*return*/, new Promise(function (resolve, reject) { return __awaiter(_this, void 0, void 0, function () {
153
153
  return __generator(this, function (_a) {
154
- createApp(folderPath, 'https://github.com/sasjs/react-seed-app', 'https://github.com/sasjs/docs', function (err) {
155
- return reject(new Error(err));
156
- });
157
- return [2 /*return*/, resolve()];
154
+ switch (_a.label) {
155
+ case 0: return [4 /*yield*/, createApp(folderPath, 'https://github.com/sasjs/react-seed-app', 'https://github.com/sasjs/docs', function (err) {
156
+ return reject(new Error(err));
157
+ })];
158
+ case 1:
159
+ _a.sent();
160
+ return [2 /*return*/, resolve()];
161
+ }
158
162
  });
159
163
  }); })];
160
164
  });
@@ -166,10 +170,14 @@ function createAngularApp(folderPath) {
166
170
  return __generator(this, function (_a) {
167
171
  return [2 /*return*/, new Promise(function (resolve, reject) { return __awaiter(_this, void 0, void 0, function () {
168
172
  return __generator(this, function (_a) {
169
- createApp(folderPath, 'https://github.com/sasjs/angular-seed-app', 'https://github.com/sasjs/docs', function (err) {
170
- return reject(new Error(err));
171
- });
172
- return [2 /*return*/, resolve()];
173
+ switch (_a.label) {
174
+ case 0: return [4 /*yield*/, createApp(folderPath, 'https://github.com/sasjs/angular-seed-app', 'https://github.com/sasjs/docs', function (err) {
175
+ return reject(new Error(err));
176
+ })];
177
+ case 1:
178
+ _a.sent();
179
+ return [2 /*return*/, resolve()];
180
+ }
173
181
  });
174
182
  }); })];
175
183
  });
@@ -181,10 +189,14 @@ function createMinimalApp(folderPath) {
181
189
  return __generator(this, function (_a) {
182
190
  return [2 /*return*/, new Promise(function (resolve, reject) { return __awaiter(_this, void 0, void 0, function () {
183
191
  return __generator(this, function (_a) {
184
- createApp(folderPath, 'https://github.com/sasjs/minimal-seed-app', 'https://github.com/sasjs/docs', function (err) {
185
- return reject(new Error(err));
186
- });
187
- return [2 /*return*/, resolve()];
192
+ switch (_a.label) {
193
+ case 0: return [4 /*yield*/, createApp(folderPath, 'https://github.com/sasjs/minimal-seed-app', 'https://github.com/sasjs/docs', function (err) {
194
+ return reject(new Error(err));
195
+ })];
196
+ case 1:
197
+ _a.sent();
198
+ return [2 /*return*/, resolve()];
199
+ }
188
200
  });
189
201
  }); })];
190
202
  });
@@ -207,9 +219,11 @@ function createTemplateApp(folderPath, template) {
207
219
  if (stderr.includes('404: Not Found') || code) {
208
220
  return [2 /*return*/, reject(new Error("Template \"".concat(template, "\" is not a SASjs template")))];
209
221
  }
210
- createApp(folderPath, "https://github.com/sasjs/template_".concat(template), 'https://github.com/sasjs/docs', function (err) {
211
- return reject(new Error(err));
212
- });
222
+ return [4 /*yield*/, createApp(folderPath, "https://github.com/sasjs/template_".concat(template), 'https://github.com/sasjs/docs', function (err) {
223
+ return reject(new Error(err));
224
+ })];
225
+ case 2:
226
+ _b.sent();
213
227
  return [2 /*return*/, resolve()];
214
228
  }
215
229
  });
@@ -225,44 +239,53 @@ function createTemplateApp(folderPath, template) {
225
239
  * @param docsUrl sasjs/docs repo url
226
240
  * @params installDependencies whether or not to do `npm install`
227
241
  */
228
- function createApp(folderPath, repoUrl, docsUrl, errorCallback, installDependencies) {
229
- if (installDependencies === void 0) { installDependencies = true; }
230
- //In past we used GIT to clone the repos. But many users potentially won't have GIT installed.
231
- //So we will use shell tools to set up the apps.
232
- var zipName = 'main.zip';
233
- var zipPath = "/archive/refs/heads/";
234
- var fullZipPath = "".concat(zipPath).concat(zipName);
235
- var spinner = (0, ora_1.default)("Creating SASjs project in ".concat(folderPath, "."));
236
- spinner.start();
237
- //Get repo zip, assuming main branch is called `main`
238
- var _a = downloadFile("".concat(repoUrl).concat(fullZipPath), zipName), stdout = _a.stdout, stderr = _a.stderr, code = _a.code;
239
- // If doesn't exist, we try again, but with master.zip for the zip name.
240
- // Since that's the name generated from branch name.
241
- if (stderr.includes('404: Not Found') || code) {
242
- zipName = 'master.zip';
243
- var _b = downloadFile("".concat(repoUrl).concat(zipPath).concat(zipName), zipName), stdout_1 = _b.stdout, stderr_1 = _b.stderr, code_1 = _b.code;
244
- if (stderr_1.includes('404: Not Found') || code_1) {
245
- errorCallback("".concat(repoUrl).concat(zipPath, " is not SASjs repository!"));
246
- return;
247
- }
248
- }
249
- var zipWithoutExtension = zipName.replace('.zip', '');
250
- var zip = new adm_zip_1.default(zipName);
251
- zip.extractAllTo("./", true);
252
- shelljs_1.default.cp('-r', "".concat(shelljs_1.default.ls('-d', "./*".concat(zipWithoutExtension))[0], "/."), folderPath);
253
- shelljs_1.default.rm('-rf', ["./*".concat(zipWithoutExtension)]);
254
- shelljs_1.default.rm('-rf', ["./".concat(zipName)]);
255
- loadDocsSubmodule(docsUrl, folderPath, fullZipPath);
256
- shelljs_1.default.rm('-f', [path_1.default.join(folderPath, '.gitmodules')]);
257
- spinner.stop();
258
- if (installDependencies) {
259
- spinner.text = 'Installing dependencies...';
260
- spinner.start();
261
- shelljs_1.default.exec("cd \"".concat(folderPath, "\" && npm install"), {
262
- silent: true
242
+ function createApp(folderPath_1, repoUrl_1, docsUrl_1, errorCallback_1) {
243
+ return __awaiter(this, arguments, void 0, function (folderPath, repoUrl, docsUrl, errorCallback, installDependencies) {
244
+ var zipName, zipPath, fullZipPath, spinner, _a, stdout, stderr, code, _b, stdout_1, stderr_1, code_1, zipWithoutExtension;
245
+ if (installDependencies === void 0) { installDependencies = true; }
246
+ return __generator(this, function (_c) {
247
+ switch (_c.label) {
248
+ case 0:
249
+ zipName = 'main.zip';
250
+ zipPath = "/archive/refs/heads/";
251
+ fullZipPath = "".concat(zipPath).concat(zipName);
252
+ spinner = (0, ora_1.default)("Creating SASjs project in ".concat(folderPath, "."));
253
+ spinner.start();
254
+ _a = downloadFile("".concat(repoUrl).concat(fullZipPath), zipName), stdout = _a.stdout, stderr = _a.stderr, code = _a.code;
255
+ // If doesn't exist, we try again, but with master.zip for the zip name.
256
+ // Since that's the name generated from branch name.
257
+ if (stderr.includes('404: Not Found') || code) {
258
+ zipName = 'master.zip';
259
+ _b = downloadFile("".concat(repoUrl).concat(zipPath).concat(zipName), zipName), stdout_1 = _b.stdout, stderr_1 = _b.stderr, code_1 = _b.code;
260
+ if (stderr_1.includes('404: Not Found') || code_1) {
261
+ errorCallback("".concat(repoUrl).concat(zipPath, " is not SASjs repository!"));
262
+ return [2 /*return*/];
263
+ }
264
+ }
265
+ zipWithoutExtension = zipName.replace('.zip', '');
266
+ return [4 /*yield*/, (0, zip_1.extractZip)(zipName, "./", true)];
267
+ case 1:
268
+ _c.sent();
269
+ shelljs_1.default.cp('-r', "".concat(shelljs_1.default.ls('-d', "./*".concat(zipWithoutExtension))[0], "/."), folderPath);
270
+ shelljs_1.default.rm('-rf', ["./*".concat(zipWithoutExtension)]);
271
+ shelljs_1.default.rm('-rf', ["./".concat(zipName)]);
272
+ return [4 /*yield*/, loadDocsSubmodule(docsUrl, folderPath, fullZipPath)];
273
+ case 2:
274
+ _c.sent();
275
+ shelljs_1.default.rm('-f', [path_1.default.join(folderPath, '.gitmodules')]);
276
+ spinner.stop();
277
+ if (installDependencies) {
278
+ spinner.text = 'Installing dependencies...';
279
+ spinner.start();
280
+ shelljs_1.default.exec("cd \"".concat(folderPath, "\" && npm install"), {
281
+ silent: true
282
+ });
283
+ spinner.stop();
284
+ }
285
+ return [2 /*return*/];
286
+ }
263
287
  });
264
- spinner.stop();
265
- }
288
+ });
266
289
  }
267
290
  /**
268
291
  * It will download and unzip `sasjs/docs` into newly created folder into `docs` subfolder
@@ -271,7 +294,7 @@ function createApp(folderPath, repoUrl, docsUrl, errorCallback, installDependenc
271
294
  * @param zipPath fixed path of where github puts zip for repo download
272
295
  */
273
296
  var loadDocsSubmodule = function (docsUrl, folderPath, zipPath) { return __awaiter(void 0, void 0, void 0, function () {
274
- var docsFolderPath, zip;
297
+ var docsFolderPath;
275
298
  return __generator(this, function (_a) {
276
299
  switch (_a.label) {
277
300
  case 0:
@@ -282,8 +305,9 @@ var loadDocsSubmodule = function (docsUrl, folderPath, zipPath) { return __await
282
305
  if (!(_a.sent()))
283
306
  docsFolderPath = "".concat(folderPath, "/docs"); // If not, we load submodule in root
284
307
  downloadFile("".concat(docsUrl).concat(zipPath), 'main.zip');
285
- zip = new adm_zip_1.default('main.zip');
286
- zip.extractAllTo('./', true);
308
+ return [4 /*yield*/, (0, zip_1.extractZip)('main.zip', './', true)];
309
+ case 2:
310
+ _a.sent();
287
311
  shelljs_1.default.cp('-r', "".concat(shelljs_1.default.ls('-d', "./*-main")[0], "/."), docsFolderPath);
288
312
  shelljs_1.default.rm('-rf', ["./*-main"]);
289
313
  shelljs_1.default.rm('-rf', ["./main.zip"]);
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Extracts a zip archive to the given directory.
3
+ *
4
+ * Replaces adm-zip's extractAllTo (GHSA-vwc7-r8mq-g2x9: extraction follows
5
+ * destination symlinks, allowing arbitrary file overwrite). This
6
+ * implementation never follows symlinks at the destination: every path
7
+ * component is checked with fs.lstatSync and extraction of an entry aborts
8
+ * if a symlink is found where a directory or file is expected.
9
+ *
10
+ * Directories mentioned only implicitly by entry paths are created; explicit
11
+ * directory entries are honoured. Unix file mode bits are preserved (needed
12
+ * by e.g. .git-hooks files in seed apps, which git requires to be
13
+ * executable). Overwriting of existing regular files is opt-in; existing
14
+ * symlinks are never written through.
15
+ *
16
+ * @param {string} zipPath - path of the zip archive to extract.
17
+ * @param {string} destDir - directory to extract into (created if missing).
18
+ * @param {boolean} overwrite - whether existing regular files are replaced.
19
+ */
20
+ export declare const extractZip: (zipPath: string, destDir: string, overwrite?: boolean) => Promise<void>;
@@ -0,0 +1,277 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __generator = (this && this.__generator) || function (thisArg, body) {
12
+ var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
13
+ return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
14
+ function verb(n) { return function (v) { return step([n, v]); }; }
15
+ function step(op) {
16
+ if (f) throw new TypeError("Generator is already executing.");
17
+ while (g && (g = 0, op[0] && (_ = 0)), _) try {
18
+ 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;
19
+ if (y = 0, t) op = [op[0] & 2, t.value];
20
+ switch (op[0]) {
21
+ case 0: case 1: t = op; break;
22
+ case 4: _.label++; return { value: op[1], done: false };
23
+ case 5: _.label++; y = op[1]; op = [0]; continue;
24
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
25
+ default:
26
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
27
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
28
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
29
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
30
+ if (t[2]) _.ops.pop();
31
+ _.trys.pop(); continue;
32
+ }
33
+ op = body.call(thisArg, _);
34
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
35
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
36
+ }
37
+ };
38
+ var __asyncValues = (this && this.__asyncValues) || function (o) {
39
+ if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
40
+ var m = o[Symbol.asyncIterator], i;
41
+ return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
42
+ function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
43
+ function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
44
+ };
45
+ var __read = (this && this.__read) || function (o, n) {
46
+ var m = typeof Symbol === "function" && o[Symbol.iterator];
47
+ if (!m) return o;
48
+ var i = m.call(o), r, ar = [], e;
49
+ try {
50
+ while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
51
+ }
52
+ catch (error) { e = { error: error }; }
53
+ finally {
54
+ try {
55
+ if (r && !r.done && (m = i["return"])) m.call(i);
56
+ }
57
+ finally { if (e) throw e.error; }
58
+ }
59
+ return ar;
60
+ };
61
+ var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
62
+ if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
63
+ if (ar || !(i in from)) {
64
+ if (!ar) ar = Array.prototype.slice.call(from, 0, i);
65
+ ar[i] = from[i];
66
+ }
67
+ }
68
+ return to.concat(ar || Array.prototype.slice.call(from));
69
+ };
70
+ var __importDefault = (this && this.__importDefault) || function (mod) {
71
+ return (mod && mod.__esModule) ? mod : { "default": mod };
72
+ };
73
+ Object.defineProperty(exports, "__esModule", { value: true });
74
+ exports.extractZip = void 0;
75
+ var fs_1 = __importDefault(require("fs"));
76
+ var path_1 = __importDefault(require("path"));
77
+ var yauzl_1 = require("yauzl");
78
+ /**
79
+ * Extracts a zip archive to the given directory.
80
+ *
81
+ * Replaces adm-zip's extractAllTo (GHSA-vwc7-r8mq-g2x9: extraction follows
82
+ * destination symlinks, allowing arbitrary file overwrite). This
83
+ * implementation never follows symlinks at the destination: every path
84
+ * component is checked with fs.lstatSync and extraction of an entry aborts
85
+ * if a symlink is found where a directory or file is expected.
86
+ *
87
+ * Directories mentioned only implicitly by entry paths are created; explicit
88
+ * directory entries are honoured. Unix file mode bits are preserved (needed
89
+ * by e.g. .git-hooks files in seed apps, which git requires to be
90
+ * executable). Overwriting of existing regular files is opt-in; existing
91
+ * symlinks are never written through.
92
+ *
93
+ * @param {string} zipPath - path of the zip archive to extract.
94
+ * @param {string} destDir - directory to extract into (created if missing).
95
+ * @param {boolean} overwrite - whether existing regular files are replaced.
96
+ */
97
+ var extractZip = function (zipPath_1, destDir_1) {
98
+ var args_1 = [];
99
+ for (var _i = 2; _i < arguments.length; _i++) {
100
+ args_1[_i - 2] = arguments[_i];
101
+ }
102
+ return __awaiter(void 0, __spreadArray([zipPath_1, destDir_1], __read(args_1), false), void 0, function (zipPath, destDir, overwrite) {
103
+ var zipfile, _a, _b, _c, entry, e_1_1;
104
+ var _d, e_1, _e, _f;
105
+ if (overwrite === void 0) { overwrite = false; }
106
+ return __generator(this, function (_g) {
107
+ switch (_g.label) {
108
+ case 0: return [4 /*yield*/, (0, yauzl_1.openPromise)(zipPath, {
109
+ lazyEntries: true,
110
+ decodeStrings: true,
111
+ validateEntrySizes: true
112
+ })];
113
+ case 1:
114
+ zipfile = _g.sent();
115
+ _g.label = 2;
116
+ case 2:
117
+ _g.trys.push([2, , 16, 17]);
118
+ _g.label = 3;
119
+ case 3:
120
+ _g.trys.push([3, 9, 10, 15]);
121
+ _a = true, _b = __asyncValues(zipfile.eachEntry());
122
+ _g.label = 4;
123
+ case 4: return [4 /*yield*/, _b.next()];
124
+ case 5:
125
+ if (!(_c = _g.sent(), _d = _c.done, !_d)) return [3 /*break*/, 8];
126
+ _f = _c.value;
127
+ _a = false;
128
+ entry = _f;
129
+ return [4 /*yield*/, extractEntry(zipfile, entry, destDir, overwrite)];
130
+ case 6:
131
+ _g.sent();
132
+ _g.label = 7;
133
+ case 7:
134
+ _a = true;
135
+ return [3 /*break*/, 4];
136
+ case 8: return [3 /*break*/, 15];
137
+ case 9:
138
+ e_1_1 = _g.sent();
139
+ e_1 = { error: e_1_1 };
140
+ return [3 /*break*/, 15];
141
+ case 10:
142
+ _g.trys.push([10, , 13, 14]);
143
+ if (!(!_a && !_d && (_e = _b.return))) return [3 /*break*/, 12];
144
+ return [4 /*yield*/, _e.call(_b)];
145
+ case 11:
146
+ _g.sent();
147
+ _g.label = 12;
148
+ case 12: return [3 /*break*/, 14];
149
+ case 13:
150
+ if (e_1) throw e_1.error;
151
+ return [7 /*endfinally*/];
152
+ case 14: return [7 /*endfinally*/];
153
+ case 15: return [3 /*break*/, 17];
154
+ case 16:
155
+ zipfile.close();
156
+ return [7 /*endfinally*/];
157
+ case 17: return [2 /*return*/];
158
+ }
159
+ });
160
+ });
161
+ };
162
+ exports.extractZip = extractZip;
163
+ var extractEntry = function (zipfile, entry, destDir, overwrite) { return __awaiter(void 0, void 0, void 0, function () {
164
+ var entryPath, isDirectoryEntry, destPath, parents, destStat, readStream, mode;
165
+ return __generator(this, function (_a) {
166
+ switch (_a.label) {
167
+ case 0:
168
+ entryPath = entry.fileName;
169
+ isDirectoryEntry = entryPath.endsWith('/');
170
+ destPath = path_1.default.join(destDir, entryPath);
171
+ parents = path_1.default.dirname(destPath);
172
+ return [4 /*yield*/, ensureDirNoSymlink(destDir, parents)];
173
+ case 1:
174
+ _a.sent();
175
+ if (isDirectoryEntry) {
176
+ // Explicit directory entry: create it (mode as-is; dirs are rwxr-xr-x by default).
177
+ fs_1.default.mkdirSync(destPath, { recursive: true });
178
+ return [2 /*return*/];
179
+ }
180
+ destStat = null;
181
+ try {
182
+ destStat = fs_1.default.lstatSync(destPath);
183
+ }
184
+ catch (_b) {
185
+ destStat = null;
186
+ }
187
+ if (destStat) {
188
+ if (destStat.isSymbolicLink()) {
189
+ throw new Error("Refusing to extract \"".concat(entryPath, "\": destination is a symbolic link."));
190
+ }
191
+ if (!destStat.isFile()) {
192
+ throw new Error("Refusing to extract \"".concat(entryPath, "\": destination is not a regular file."));
193
+ }
194
+ if (!overwrite)
195
+ return [2 /*return*/];
196
+ }
197
+ return [4 /*yield*/, zipfile.openReadStreamPromise(entry)];
198
+ case 2:
199
+ readStream = _a.sent();
200
+ return [4 /*yield*/, streamToFile(readStream, destPath)
201
+ // Preserve unix mode bits (external attrs high 16 bits), e.g. the exec bit
202
+ // on .git-hooks files in seed apps. Only entries created on a unix host
203
+ // (versionMadeBy platform 3) are honoured: archives created on other
204
+ // hosts (Windows, DOS, ...) leave these bits zero or repurpose them, and
205
+ // chmod-ing a mode derived from them would give the file garbage
206
+ // permissions.
207
+ ];
208
+ case 3:
209
+ _a.sent();
210
+ mode = entry.versionMadeBy >>> 8 === 3
211
+ ? (entry.externalFileAttributes >>> 16) & 4095
212
+ : 0;
213
+ if (mode)
214
+ fs_1.default.chmodSync(destPath, mode);
215
+ return [2 /*return*/];
216
+ }
217
+ });
218
+ }); };
219
+ /**
220
+ * Creates dirPath (which must be at or under rootDir), creating parents as
221
+ * needed, and throws if any existing path component is a symbolic link.
222
+ *
223
+ * NOTE: the existsSync/lstatSync/mkdirSync sequence is inherently racy
224
+ * (TOCTOU) - a symlink swapped in between the check and the mkdir is
225
+ * traversed. Extraction is therefore strictly sequential, one entry at a
226
+ * time. Do not extract entries in parallel into a shared destDir without
227
+ * closing this window first.
228
+ */
229
+ var ensureDirNoSymlink = function (rootDir, dirPath) { return __awaiter(void 0, void 0, void 0, function () {
230
+ var rootParts, targetParts, current, i, exists, stat;
231
+ return __generator(this, function (_a) {
232
+ rootParts = path_1.default.resolve(rootDir).split(path_1.default.sep);
233
+ targetParts = path_1.default.resolve(dirPath).split(path_1.default.sep);
234
+ current = rootParts[0] || path_1.default.sep;
235
+ for (i = 1; i < targetParts.length; i++) {
236
+ current = path_1.default.join(current, targetParts[i]);
237
+ exists = fs_1.default.existsSync(current);
238
+ if (exists) {
239
+ stat = fs_1.default.lstatSync(current);
240
+ if (stat.isSymbolicLink()) {
241
+ throw new Error("Refusing to extract into \"".concat(current, "\": symbolic link in destination path."));
242
+ }
243
+ if (!stat.isDirectory()) {
244
+ throw new Error("Cannot extract into \"".concat(current, "\": exists and is not a directory."));
245
+ }
246
+ }
247
+ else {
248
+ fs_1.default.mkdirSync(current, { recursive: false });
249
+ }
250
+ }
251
+ return [2 /*return*/];
252
+ });
253
+ }); };
254
+ var streamToFile = function (readStream, destPath) { return __awaiter(void 0, void 0, void 0, function () {
255
+ var writeStream;
256
+ return __generator(this, function (_a) {
257
+ switch (_a.label) {
258
+ case 0:
259
+ writeStream = fs_1.default.createWriteStream(destPath, {
260
+ flags: 'w',
261
+ mode: 438
262
+ });
263
+ return [4 /*yield*/, pipelinePromise(readStream, writeStream)];
264
+ case 1:
265
+ _a.sent();
266
+ return [2 /*return*/];
267
+ }
268
+ });
269
+ }); };
270
+ var pipelinePromise = function (readable, writable) {
271
+ return new Promise(function (resolve, reject) {
272
+ readable.on('error', reject);
273
+ writable.on('error', reject);
274
+ writable.on('close', resolve);
275
+ readable.pipe(writable);
276
+ });
277
+ };
package/package.json CHANGED
@@ -70,10 +70,9 @@
70
70
  },
71
71
  "dependencies": {
72
72
  "@sasjs/adapter": "^4.19.0",
73
- "@sasjs/core": "5.2.4",
73
+ "@sasjs/core": "5.2.9",
74
74
  "@sasjs/lint": "2.5.0",
75
75
  "@sasjs/utils": "3.6.2",
76
- "adm-zip": "0.6.0",
77
76
  "chalk": "4.1.2",
78
77
  "dotenv": "17.4.2",
79
78
  "find": "0.3.0",
@@ -89,13 +88,15 @@
89
88
  "shelljs": "0.10.0",
90
89
  "ssl-root-cas": "1.3.1",
91
90
  "xml": "1.0.1",
92
- "yargs": "17.6.2"
91
+ "yargs": "17.6.2",
92
+ "yauzl": "3.4.0",
93
+ "yazl": "3.3.1"
93
94
  },
94
95
  "devDependencies": {
95
96
  "@babel/core": "7.29.7",
96
97
  "@babel/preset-env": "7.29.7",
97
98
  "@babel/preset-typescript": "7.29.7",
98
- "@types/adm-zip": "0.5.8",
99
+ "@nogoo9/gitleaks": "8.30.1-post.2",
99
100
  "@types/debug": "4.1.13",
100
101
  "@types/find": "0.2.4",
101
102
  "@types/jest": "30.0.0",
@@ -106,6 +107,8 @@
106
107
  "@types/shelljs": "0.8.10",
107
108
  "@types/url-parse": "1.4.11",
108
109
  "@types/xml": "1.0.11",
110
+ "@types/yauzl": "3.4.0",
111
+ "@types/yazl": "3.3.1",
109
112
  "babel-jest": "30.5.1",
110
113
  "copyfiles": "2.4.1",
111
114
  "jest": "30.5.1",
@@ -124,5 +127,5 @@
124
127
  "ws": "8.21.0",
125
128
  "nanoid": "3.3.18"
126
129
  },
127
- "version": "4.20.2"
130
+ "version": "4.20.4"
128
131
  }