@sasjs/cli 4.20.2 → 4.20.3
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 +11 -0
- package/build/utils/compressAndSave.js +9 -4
- package/build/utils/spec/zip.spec.d.ts +1 -0
- package/build/utils/spec/zip.spec.js +282 -0
- package/build/utils/utils.js +80 -56
- package/build/utils/zip.d.ts +20 -0
- package/build/utils/zip.js +277 -0
- package/package.json +7 -4
package/README.md
CHANGED
|
@@ -53,6 +53,17 @@ 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
|
+
1. Clone the repo and install dependencies (`npm install`).
|
|
59
|
+
2. One-time setup: point git at this repo's hooks directory:
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
git config core.hooksPath ./.git-hooks
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
This enables the pre-commit checks (gitleaks secret scan, 2MB commit size limit). This step is required because the repo's `.npmrc` sets `ignore-scripts=true`, so the `prepare` script that would configure the hooks path automatically never runs on install. If you skip it, your commits are not scanned for secrets and you may push them that way.
|
|
66
|
+
|
|
56
67
|
## Star Gazing
|
|
57
68
|
|
|
58
69
|
If you find this library useful, please leave a star and help us grow our star graph!
|
|
@@ -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
|
|
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
|
|
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.
|
|
60
|
-
return [4 /*yield*/,
|
|
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
|
+
});
|
package/build/utils/utils.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
155
|
-
return
|
|
156
|
-
|
|
157
|
-
|
|
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
|
-
|
|
170
|
-
return
|
|
171
|
-
|
|
172
|
-
|
|
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
|
-
|
|
185
|
-
return
|
|
186
|
-
|
|
187
|
-
|
|
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
|
-
|
|
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(
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
286
|
-
|
|
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
|
@@ -73,7 +73,6 @@
|
|
|
73
73
|
"@sasjs/core": "5.2.4",
|
|
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
|
-
"@
|
|
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.
|
|
130
|
+
"version": "4.20.3"
|
|
128
131
|
}
|