@sasjs/cli 6.19.4 → 6.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/commands/build/internal/mockSite/assets/intercept.js +4 -1
- package/build/commands/build/internal/mockSite/assets/sw.js +49 -22
- package/build/commands/build/internal/mockSite/spec/intercept.spec.js +23 -2
- package/build/commands/build/internal/mockSite/spec/sw.spec.js +190 -67
- package/build/commands/help/help.js +2 -1
- package/build/commands/lint/lintCommand.js +74 -13
- package/build/commands/lint/processLint.d.ts +4 -0
- package/build/commands/lint/processLint.js +19 -10
- package/build/commands/lint/spec/lintCommand.spec.js +126 -12
- package/build/commands/lint/spec/processLint.spec.d.ts +1 -0
- package/build/commands/lint/spec/processLint.spec.js +127 -0
- package/package.json +2 -2
|
@@ -125,8 +125,11 @@
|
|
|
125
125
|
xhr.dispatchEvent(new ProgressEvent('loadend'))
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
+
// `/api` is the short spelling of the API and `/SASjsApi` the canonical one:
|
|
129
|
+
// the build bridges both, so a caller that uses either reaches the mock.
|
|
128
130
|
const isSasjs = (url) =>
|
|
129
|
-
typeof url === 'string' &&
|
|
131
|
+
typeof url === 'string' &&
|
|
132
|
+
(url.indexOf('/SASjsApi/') >= 0 || url.indexOf('/api/') >= 0)
|
|
130
133
|
|
|
131
134
|
const proto = XMLHttpRequest.prototype
|
|
132
135
|
const nativeOpen = proto.open
|
|
@@ -327,6 +327,15 @@ const RUNTIME = new BrowserRuntime()
|
|
|
327
327
|
*/
|
|
328
328
|
const BASE = (self.location.pathname || '').replace(/\/sw\.js.*$/, '')
|
|
329
329
|
|
|
330
|
+
/**
|
|
331
|
+
* The short spelling of the App Stream landing page.
|
|
332
|
+
*
|
|
333
|
+
* It is the default: the interface links to `/app`, and sasjs/server serves it
|
|
334
|
+
* there while keeping `/AppStream` as a redirect. The worker mirrors that, so a
|
|
335
|
+
* link the interface builds reaches the same page on the static demo.
|
|
336
|
+
*/
|
|
337
|
+
const SHORT_APP_STREAM_PATH = '/app'
|
|
338
|
+
|
|
330
339
|
const withoutBase = (pathname) =>
|
|
331
340
|
BASE && pathname.startsWith(BASE)
|
|
332
341
|
? pathname.slice(BASE.length) || '/'
|
|
@@ -889,6 +898,11 @@ const contentsResponse = (answer, path) => {
|
|
|
889
898
|
|
|
890
899
|
const routeRequest = async (request, url) => {
|
|
891
900
|
const path = withoutBase(url.pathname).replace(/\/+$/, '') || '/'
|
|
901
|
+
// `/api` is the short spelling of the API, and `/SASjsApi` the canonical one:
|
|
902
|
+
// both are answered, so the alias is stripped once here and everything below -
|
|
903
|
+
// the route lookup, the drive routes' plain-text refusals, the log - works
|
|
904
|
+
// from one path.
|
|
905
|
+
const apiPath = path.replace(/^\/api(?=\/|$)/i, '/SASjsApi')
|
|
892
906
|
const query = {}
|
|
893
907
|
for (const [k, v] of url.searchParams.entries()) query[k] = v
|
|
894
908
|
|
|
@@ -901,27 +915,31 @@ const routeRequest = async (request, url) => {
|
|
|
901
915
|
// every visit. Unanswered, those requests reach the static host and the page
|
|
902
916
|
// parses this site's index.html as JSON.
|
|
903
917
|
//
|
|
904
|
-
//
|
|
905
|
-
//
|
|
906
|
-
//
|
|
907
|
-
//
|
|
908
|
-
//
|
|
909
|
-
//
|
|
910
|
-
//
|
|
911
|
-
//
|
|
912
|
-
//
|
|
913
|
-
//
|
|
918
|
+
// `/app` is the spelling the interface links to and the server serves; the
|
|
919
|
+
// canonical `/AppStream` still answers, because a client built before the
|
|
920
|
+
// short paths existed asks for it. The answers are the same either way, so
|
|
921
|
+
// `/AppStream` redirects to `/app` - exactly as the server redirects it -
|
|
922
|
+
// and the landing page, the listing and the missing-name refusal are decided
|
|
923
|
+
// on both.
|
|
924
|
+
//
|
|
925
|
+
// The listing is empty rather than read from `registry()`, which records this
|
|
926
|
+
// deployment's own folder so the delete route has something to remove: the
|
|
927
|
+
// stream really does serve nothing at `/app/<name>/` here, and a listing that
|
|
928
|
+
// named it would offer a tile that 404s.
|
|
929
|
+
// `path` has its trailing slashes stripped, and the landing answers differ by
|
|
930
|
+
// exactly that, so they are decided on the request's own pathname - minus the
|
|
931
|
+
// folder the site is served from, which the patterns do not name.
|
|
914
932
|
const stream = withoutBase(url.pathname)
|
|
915
933
|
|
|
916
934
|
if (/^\/AppStream$/i.test(stream))
|
|
917
935
|
return new Response(null, {
|
|
918
936
|
status: 301,
|
|
919
|
-
headers: { location: `${BASE}
|
|
937
|
+
headers: { location: `${BASE}${SHORT_APP_STREAM_PATH}` }
|
|
920
938
|
})
|
|
921
939
|
|
|
922
940
|
if (
|
|
923
|
-
/^\/AppStream
|
|
924
|
-
/^\/AppStream\/index\.html$/i.test(stream)
|
|
941
|
+
/^\/(app|AppStream)\/?$/i.test(stream) ||
|
|
942
|
+
/^\/(app|AppStream)\/index\.html$/i.test(stream)
|
|
925
943
|
)
|
|
926
944
|
return new Response(
|
|
927
945
|
[
|
|
@@ -934,9 +952,10 @@ const routeRequest = async (request, url) => {
|
|
|
934
952
|
{ status: 200, headers: { 'content-type': 'text/html; charset=utf-8' } }
|
|
935
953
|
)
|
|
936
954
|
|
|
937
|
-
if (/^\/AppStream\/apps\.json$/i.test(stream))
|
|
955
|
+
if (/^\/(app|AppStream)\/apps\.json$/i.test(stream))
|
|
956
|
+
return json({ apps: [] })
|
|
938
957
|
|
|
939
|
-
if (/^\/AppStream\//i.test(stream)) {
|
|
958
|
+
if (/^\/(app|AppStream)\//i.test(stream)) {
|
|
940
959
|
return json(
|
|
941
960
|
{
|
|
942
961
|
message:
|
|
@@ -948,8 +967,9 @@ const routeRequest = async (request, url) => {
|
|
|
948
967
|
|
|
949
968
|
// The package's own matcher, so a route is recognised however its path is
|
|
950
969
|
// cased and its parameters are read the way the document states them.
|
|
951
|
-
const matched = API.findRoute(request.method,
|
|
952
|
-
if (!matched)
|
|
970
|
+
const matched = API.findRoute(request.method, apiPath)
|
|
971
|
+
if (!matched)
|
|
972
|
+
return json({ message: 'Not part of the API: ' + apiPath }, 404)
|
|
953
973
|
|
|
954
974
|
const routeId = matched.route.id
|
|
955
975
|
const subject = await subjectOf(request, query)
|
|
@@ -1023,7 +1043,7 @@ const routeRequest = async (request, url) => {
|
|
|
1023
1043
|
// envelope the other routes already answer with.
|
|
1024
1044
|
if (API.isApiError(e)) {
|
|
1025
1045
|
if (e.body !== undefined) return json(e.body, e.status)
|
|
1026
|
-
if (
|
|
1046
|
+
if (apiPath.includes('/SASjsApi/drive/')) return text(e.message, e.status)
|
|
1027
1047
|
return json({ message: e.message }, e.status)
|
|
1028
1048
|
}
|
|
1029
1049
|
return json(
|
|
@@ -1079,10 +1099,17 @@ self.addEventListener('fetch', (event) => {
|
|
|
1079
1099
|
// The host's own paths are answered here, including one the table does not
|
|
1080
1100
|
// have - falling through to the static host would put the SPA's index.html
|
|
1081
1101
|
// behind a typo in a caller's URL, or behind a listing an interface parses as
|
|
1082
|
-
// JSON. `/
|
|
1083
|
-
//
|
|
1084
|
-
//
|
|
1085
|
-
|
|
1102
|
+
// JSON. `/app` is one of those: the package serves app deployments, but the
|
|
1103
|
+
// listing is the server's web route, and an interface that lists apps asks for
|
|
1104
|
+
// it on every visit.
|
|
1105
|
+
//
|
|
1106
|
+
// `/api` and `/api/` on their own are the API's documentation page - a file
|
|
1107
|
+
// the build copies into the site - so the worker stays out of the way there
|
|
1108
|
+
// and the static host answers it. Every other `/api/...` is an API call on the
|
|
1109
|
+
// short spelling, which routeRequest canonicalises to `/SASjsApi`.
|
|
1110
|
+
if (/^\/api\/?$/i.test(path)) return
|
|
1111
|
+
|
|
1112
|
+
if (!/^\/(SASjsApi|SASLogon|AppStream|api|app)(\/|$)/i.test(path)) return
|
|
1086
1113
|
event.respondWith(handle(event.request, url))
|
|
1087
1114
|
})
|
|
1088
1115
|
|
|
@@ -173,13 +173,34 @@ describe('page-side bridge', function () {
|
|
|
173
173
|
fetchImpl = jest.fn();
|
|
174
174
|
XHR = loadBridge(fetchImpl).XHR;
|
|
175
175
|
xhr = new XHR();
|
|
176
|
-
xhr.open('GET', '/
|
|
176
|
+
xhr.open('GET', '/health');
|
|
177
177
|
xhr.send();
|
|
178
178
|
expect(xhr.sentWith).toBeUndefined();
|
|
179
179
|
expect(fetchImpl).not.toHaveBeenCalled();
|
|
180
180
|
return [2 /*return*/];
|
|
181
181
|
});
|
|
182
182
|
}); });
|
|
183
|
+
it('bridges the short API spelling as well as the canonical one', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
184
|
+
var fetchImpl, XHR, xhr, _a, url;
|
|
185
|
+
return __generator(this, function (_b) {
|
|
186
|
+
switch (_b.label) {
|
|
187
|
+
case 0:
|
|
188
|
+
fetchImpl = jest.fn(function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
|
|
189
|
+
return [2 /*return*/, jsonResponse('{"status":"ok"}')];
|
|
190
|
+
}); }); });
|
|
191
|
+
XHR = loadBridge(fetchImpl).XHR;
|
|
192
|
+
xhr = new XHR();
|
|
193
|
+
return [4 /*yield*/, run(xhr, 'GET', '/api/info')];
|
|
194
|
+
case 1:
|
|
195
|
+
_b.sent();
|
|
196
|
+
_a = __read(fetchImpl.mock.calls[0], 1), url = _a[0];
|
|
197
|
+
expect(url).toBe('/api/info');
|
|
198
|
+
expect(xhr.status).toBe(200);
|
|
199
|
+
expect(xhr.responseText).toBe('{"status":"ok"}');
|
|
200
|
+
return [2 /*return*/];
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
}); });
|
|
183
204
|
it('converts a multipart body to JSON and replays the response onto the XHR', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
184
205
|
var fetchImpl, XHR, form, xhr, _a, init;
|
|
185
206
|
return __generator(this, function (_b) {
|
|
@@ -286,7 +307,7 @@ describe('page-side bridge', function () {
|
|
|
286
307
|
expect(second.XHR.prototype).not.toBe(first.XHR.prototype);
|
|
287
308
|
expect(Object.getPrototypeOf(second.XHR.prototype)).toBe(FakeXHR.prototype);
|
|
288
309
|
var xhr = new second.XHR();
|
|
289
|
-
xhr.open('GET', '/
|
|
310
|
+
xhr.open('GET', '/health');
|
|
290
311
|
xhr.send();
|
|
291
312
|
expect(xhr.nativeSendCount).toBe(1);
|
|
292
313
|
});
|
|
@@ -35,6 +35,17 @@ var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
|
35
35
|
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
36
36
|
}
|
|
37
37
|
};
|
|
38
|
+
var __values = (this && this.__values) || function(o) {
|
|
39
|
+
var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
|
|
40
|
+
if (m) return m.call(o);
|
|
41
|
+
if (o && typeof o.length === "number") return {
|
|
42
|
+
next: function () {
|
|
43
|
+
if (o && i >= o.length) o = void 0;
|
|
44
|
+
return { value: o && o[i++], done: !o };
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
|
|
48
|
+
};
|
|
38
49
|
var __read = (this && this.__read) || function (o, n) {
|
|
39
50
|
var m = typeof Symbol === "function" && o[Symbol.iterator];
|
|
40
51
|
if (!m) return o;
|
|
@@ -51,17 +62,6 @@ var __read = (this && this.__read) || function (o, n) {
|
|
|
51
62
|
}
|
|
52
63
|
return ar;
|
|
53
64
|
};
|
|
54
|
-
var __values = (this && this.__values) || function(o) {
|
|
55
|
-
var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
|
|
56
|
-
if (m) return m.call(o);
|
|
57
|
-
if (o && typeof o.length === "number") return {
|
|
58
|
-
next: function () {
|
|
59
|
-
if (o && i >= o.length) o = void 0;
|
|
60
|
-
return { value: o && o[i++], done: !o };
|
|
61
|
-
}
|
|
62
|
-
};
|
|
63
|
-
throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
|
|
64
|
-
};
|
|
65
65
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
66
66
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
67
67
|
};
|
|
@@ -898,51 +898,59 @@ describe('service worker host', function () {
|
|
|
898
898
|
});
|
|
899
899
|
}); });
|
|
900
900
|
it('answers every route under the folder the site is published in', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
901
|
-
var worker, info, _a, page, folder, stream, landing, apps, _b;
|
|
902
|
-
var
|
|
903
|
-
return __generator(this, function (
|
|
904
|
-
switch (
|
|
901
|
+
var worker, info, _a, page, folder, stream, landing, apps, _b, aliased, _c;
|
|
902
|
+
var _d;
|
|
903
|
+
return __generator(this, function (_e) {
|
|
904
|
+
switch (_e.label) {
|
|
905
905
|
case 0:
|
|
906
906
|
worker = loadWorker('/demo');
|
|
907
|
-
worker.runtime.load((
|
|
908
|
-
|
|
909
|
-
|
|
907
|
+
worker.runtime.load((_d = {},
|
|
908
|
+
_d[drivePath('services/public/getdata.js')] = '_webout = "seeded";',
|
|
909
|
+
_d));
|
|
910
910
|
return [4 /*yield*/, worker.dispatch('/demo/SASjsApi/info')];
|
|
911
911
|
case 1:
|
|
912
|
-
info =
|
|
912
|
+
info = _e.sent();
|
|
913
913
|
expect(info.status).toBe(200);
|
|
914
914
|
_a = expect;
|
|
915
915
|
return [4 /*yield*/, info.json()];
|
|
916
916
|
case 2:
|
|
917
|
-
_a.apply(void 0, [(
|
|
917
|
+
_a.apply(void 0, [(_e.sent()).mode]).toBe('desktop');
|
|
918
918
|
return [4 /*yield*/, worker.dispatch('/demo/')];
|
|
919
919
|
case 3:
|
|
920
|
-
page =
|
|
920
|
+
page = _e.sent();
|
|
921
921
|
expect(page.status).toBe(200);
|
|
922
922
|
expect(page.headers.get('content-type')).toContain('text/html');
|
|
923
923
|
return [4 /*yield*/, worker.dispatch('/demo')];
|
|
924
924
|
case 4:
|
|
925
|
-
folder =
|
|
925
|
+
folder = _e.sent();
|
|
926
926
|
expect(folder.status).toBe(200);
|
|
927
927
|
expect(folder.headers.get('content-type')).toContain('text/html');
|
|
928
928
|
return [4 /*yield*/, worker.dispatch('/demo/AppStream')];
|
|
929
929
|
case 5:
|
|
930
|
-
stream =
|
|
930
|
+
stream = _e.sent();
|
|
931
931
|
expect(stream.status).toBe(301);
|
|
932
|
-
expect(stream.headers.get('location')).toBe('/demo/
|
|
933
|
-
return [4 /*yield*/, worker.dispatch('/demo/
|
|
932
|
+
expect(stream.headers.get('location')).toBe('/demo/app');
|
|
933
|
+
return [4 /*yield*/, worker.dispatch('/demo/app')];
|
|
934
934
|
case 6:
|
|
935
|
-
landing =
|
|
935
|
+
landing = _e.sent();
|
|
936
936
|
expect(landing.status).toBe(200);
|
|
937
937
|
expect(landing.headers.get('content-type')).toContain('text/html');
|
|
938
|
-
return [4 /*yield*/, worker.dispatch('/demo/
|
|
938
|
+
return [4 /*yield*/, worker.dispatch('/demo/app/apps.json')];
|
|
939
939
|
case 7:
|
|
940
|
-
apps =
|
|
940
|
+
apps = _e.sent();
|
|
941
941
|
expect(apps.status).toBe(200);
|
|
942
942
|
_b = expect;
|
|
943
943
|
return [4 /*yield*/, apps.json()];
|
|
944
944
|
case 8:
|
|
945
|
-
_b.apply(void 0, [
|
|
945
|
+
_b.apply(void 0, [_e.sent()]).toEqual({ apps: [] });
|
|
946
|
+
return [4 /*yield*/, worker.dispatch('/demo/api/info')];
|
|
947
|
+
case 9:
|
|
948
|
+
aliased = _e.sent();
|
|
949
|
+
expect(aliased.status).toBe(200);
|
|
950
|
+
_c = expect;
|
|
951
|
+
return [4 /*yield*/, aliased.json()];
|
|
952
|
+
case 10:
|
|
953
|
+
_c.apply(void 0, [(_e.sent()).mode]).toBe('desktop');
|
|
946
954
|
// a path outside the folder is not the site's to answer
|
|
947
955
|
expect(worker.dispatch('/other/SASjsApi/info')).toBeUndefined();
|
|
948
956
|
return [2 /*return*/];
|
|
@@ -1333,42 +1341,157 @@ describe('service worker host', function () {
|
|
|
1333
1341
|
}
|
|
1334
1342
|
});
|
|
1335
1343
|
}); });
|
|
1336
|
-
it('answers the app stream
|
|
1337
|
-
var dispatch, listing,
|
|
1338
|
-
|
|
1339
|
-
|
|
1344
|
+
it('answers the app stream on the short path, and the canonical one as an alias', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
1345
|
+
var dispatch, _a, _b, path_2, listing, _c, e_1_1, _d, _e, path_3, landing, _f, e_2_1, root, canonicalLanding, _g, _h, _j, path_4, app, _k, e_3_1;
|
|
1346
|
+
var e_1, _l, e_2, _m, e_3, _o;
|
|
1347
|
+
return __generator(this, function (_p) {
|
|
1348
|
+
switch (_p.label) {
|
|
1340
1349
|
case 0:
|
|
1341
1350
|
dispatch = seeded().dispatch;
|
|
1342
|
-
|
|
1351
|
+
_p.label = 1;
|
|
1343
1352
|
case 1:
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
return [4 /*yield*/, listing.json()];
|
|
1353
|
+
_p.trys.push([1, 7, 8, 9]);
|
|
1354
|
+
_a = __values(['/app/apps.json', '/AppStream/apps.json']), _b = _a.next();
|
|
1355
|
+
_p.label = 2;
|
|
1348
1356
|
case 2:
|
|
1349
|
-
|
|
1350
|
-
|
|
1357
|
+
if (!!_b.done) return [3 /*break*/, 6];
|
|
1358
|
+
path_2 = _b.value;
|
|
1359
|
+
return [4 /*yield*/, dispatch(path_2)];
|
|
1351
1360
|
case 3:
|
|
1352
|
-
|
|
1353
|
-
expect(
|
|
1354
|
-
expect
|
|
1355
|
-
return [4 /*yield*/,
|
|
1361
|
+
listing = _p.sent();
|
|
1362
|
+
expect(listing.status).toBe(200);
|
|
1363
|
+
_c = expect;
|
|
1364
|
+
return [4 /*yield*/, listing.json()];
|
|
1356
1365
|
case 4:
|
|
1357
|
-
|
|
1366
|
+
_c.apply(void 0, [_p.sent()]).toEqual({ apps: [] });
|
|
1367
|
+
_p.label = 5;
|
|
1368
|
+
case 5:
|
|
1369
|
+
_b = _a.next();
|
|
1370
|
+
return [3 /*break*/, 2];
|
|
1371
|
+
case 6: return [3 /*break*/, 9];
|
|
1372
|
+
case 7:
|
|
1373
|
+
e_1_1 = _p.sent();
|
|
1374
|
+
e_1 = { error: e_1_1 };
|
|
1375
|
+
return [3 /*break*/, 9];
|
|
1376
|
+
case 8:
|
|
1377
|
+
try {
|
|
1378
|
+
if (_b && !_b.done && (_l = _a.return)) _l.call(_a);
|
|
1379
|
+
}
|
|
1380
|
+
finally { if (e_1) throw e_1.error; }
|
|
1381
|
+
return [7 /*endfinally*/];
|
|
1382
|
+
case 9:
|
|
1383
|
+
_p.trys.push([9, 15, 16, 17]);
|
|
1384
|
+
_d = __values(['/app', '/app/', '/app/index.html']), _e = _d.next();
|
|
1385
|
+
_p.label = 10;
|
|
1386
|
+
case 10:
|
|
1387
|
+
if (!!_e.done) return [3 /*break*/, 14];
|
|
1388
|
+
path_3 = _e.value;
|
|
1389
|
+
return [4 /*yield*/, dispatch(path_3)];
|
|
1390
|
+
case 11:
|
|
1391
|
+
landing = _p.sent();
|
|
1358
1392
|
expect(landing.status).toBe(200);
|
|
1359
1393
|
expect(landing.headers.get('content-type')).toContain('text/html');
|
|
1360
|
-
|
|
1394
|
+
_f = expect;
|
|
1361
1395
|
return [4 /*yield*/, landing.text()];
|
|
1362
|
-
case
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
case
|
|
1366
|
-
|
|
1396
|
+
case 12:
|
|
1397
|
+
_f.apply(void 0, [_p.sent()]).toContain('serves no apps');
|
|
1398
|
+
_p.label = 13;
|
|
1399
|
+
case 13:
|
|
1400
|
+
_e = _d.next();
|
|
1401
|
+
return [3 /*break*/, 10];
|
|
1402
|
+
case 14: return [3 /*break*/, 17];
|
|
1403
|
+
case 15:
|
|
1404
|
+
e_2_1 = _p.sent();
|
|
1405
|
+
e_2 = { error: e_2_1 };
|
|
1406
|
+
return [3 /*break*/, 17];
|
|
1407
|
+
case 16:
|
|
1408
|
+
try {
|
|
1409
|
+
if (_e && !_e.done && (_m = _d.return)) _m.call(_d);
|
|
1410
|
+
}
|
|
1411
|
+
finally { if (e_2) throw e_2.error; }
|
|
1412
|
+
return [7 /*endfinally*/];
|
|
1413
|
+
case 17: return [4 /*yield*/, dispatch('/AppStream')];
|
|
1414
|
+
case 18:
|
|
1415
|
+
root = _p.sent();
|
|
1416
|
+
expect(root.status).toBe(301);
|
|
1417
|
+
expect(root.headers.get('location')).toBe('/app');
|
|
1418
|
+
return [4 /*yield*/, dispatch('/AppStream/')];
|
|
1419
|
+
case 19:
|
|
1420
|
+
canonicalLanding = _p.sent();
|
|
1421
|
+
expect(canonicalLanding.status).toBe(200);
|
|
1422
|
+
_g = expect;
|
|
1423
|
+
return [4 /*yield*/, canonicalLanding.text()];
|
|
1424
|
+
case 20:
|
|
1425
|
+
_g.apply(void 0, [_p.sent()]).toContain('serves no apps');
|
|
1426
|
+
_p.label = 21;
|
|
1427
|
+
case 21:
|
|
1428
|
+
_p.trys.push([21, 27, 28, 29]);
|
|
1429
|
+
_h = __values(['/app/Demo/', '/AppStream/Demo/']), _j = _h.next();
|
|
1430
|
+
_p.label = 22;
|
|
1431
|
+
case 22:
|
|
1432
|
+
if (!!_j.done) return [3 /*break*/, 26];
|
|
1433
|
+
path_4 = _j.value;
|
|
1434
|
+
return [4 /*yield*/, dispatch(path_4)];
|
|
1435
|
+
case 23:
|
|
1436
|
+
app = _p.sent();
|
|
1367
1437
|
expect(app.status).toBe(404);
|
|
1368
|
-
|
|
1438
|
+
_k = expect;
|
|
1369
1439
|
return [4 /*yield*/, app.json()];
|
|
1370
|
-
case
|
|
1371
|
-
|
|
1440
|
+
case 24:
|
|
1441
|
+
_k.apply(void 0, [(_p.sent()).message]).toContain('serves no apps');
|
|
1442
|
+
_p.label = 25;
|
|
1443
|
+
case 25:
|
|
1444
|
+
_j = _h.next();
|
|
1445
|
+
return [3 /*break*/, 22];
|
|
1446
|
+
case 26: return [3 /*break*/, 29];
|
|
1447
|
+
case 27:
|
|
1448
|
+
e_3_1 = _p.sent();
|
|
1449
|
+
e_3 = { error: e_3_1 };
|
|
1450
|
+
return [3 /*break*/, 29];
|
|
1451
|
+
case 28:
|
|
1452
|
+
try {
|
|
1453
|
+
if (_j && !_j.done && (_o = _h.return)) _o.call(_h);
|
|
1454
|
+
}
|
|
1455
|
+
finally { if (e_3) throw e_3.error; }
|
|
1456
|
+
return [7 /*endfinally*/];
|
|
1457
|
+
case 29: return [2 /*return*/];
|
|
1458
|
+
}
|
|
1459
|
+
});
|
|
1460
|
+
}); });
|
|
1461
|
+
it('answers the API on /api as well as on /SASjsApi', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
1462
|
+
var dispatch, canonical, short, _a, _b, _c, missing, _d;
|
|
1463
|
+
return __generator(this, function (_e) {
|
|
1464
|
+
switch (_e.label) {
|
|
1465
|
+
case 0:
|
|
1466
|
+
dispatch = seeded().dispatch;
|
|
1467
|
+
return [4 /*yield*/, dispatch('/SASjsApi/info')];
|
|
1468
|
+
case 1:
|
|
1469
|
+
canonical = _e.sent();
|
|
1470
|
+
return [4 /*yield*/, dispatch('/api/info')];
|
|
1471
|
+
case 2:
|
|
1472
|
+
short = _e.sent();
|
|
1473
|
+
expect(short.status).toBe(200);
|
|
1474
|
+
_b = expect;
|
|
1475
|
+
return [4 /*yield*/, short.json()];
|
|
1476
|
+
case 3:
|
|
1477
|
+
_c = (_a = _b.apply(void 0, [_e.sent()])).toEqual;
|
|
1478
|
+
return [4 /*yield*/, canonical.json()];
|
|
1479
|
+
case 4:
|
|
1480
|
+
_c.apply(_a, [_e.sent()]);
|
|
1481
|
+
return [4 /*yield*/, dispatch('/api/drive/file?_filePath=' + encodeURIComponent('/missing.txt'))];
|
|
1482
|
+
case 5:
|
|
1483
|
+
missing = _e.sent();
|
|
1484
|
+
expect(missing.status).toBe(404);
|
|
1485
|
+
expect(missing.headers.get('content-type')).toContain('text/plain');
|
|
1486
|
+
_d = expect;
|
|
1487
|
+
return [4 /*yield*/, missing.text()];
|
|
1488
|
+
case 6:
|
|
1489
|
+
_d.apply(void 0, [_e.sent()]).toBe("File doesn't exist.");
|
|
1490
|
+
// The documentation page is a file the build copies into the site, so the
|
|
1491
|
+
// worker leaves `/api` and `/api/` to the static host instead of answering
|
|
1492
|
+
// them as routes.
|
|
1493
|
+
expect(dispatch('/api')).toBeUndefined();
|
|
1494
|
+
expect(dispatch('/api/')).toBeUndefined();
|
|
1372
1495
|
return [2 /*return*/];
|
|
1373
1496
|
}
|
|
1374
1497
|
});
|
|
@@ -2223,7 +2346,7 @@ describe('service worker host', function () {
|
|
|
2223
2346
|
}); });
|
|
2224
2347
|
});
|
|
2225
2348
|
describe('conformance corpus', function () {
|
|
2226
|
-
var
|
|
2349
|
+
var e_4, _a;
|
|
2227
2350
|
/**
|
|
2228
2351
|
* The corpus is what "the same API" means: the cases @sasjs/api runs against
|
|
2229
2352
|
* its own services, and against a live sasjs/server, run here against this
|
|
@@ -2232,7 +2355,7 @@ describe('service worker host', function () {
|
|
|
2232
2355
|
* used to have - and it is a test rather than a review.
|
|
2233
2356
|
*/
|
|
2234
2357
|
var httpFor = function (testCase) {
|
|
2235
|
-
var
|
|
2358
|
+
var e_5, _a;
|
|
2236
2359
|
var _b;
|
|
2237
2360
|
var _c = __read(testCase.route.split(' '), 2), method = _c[0], routePath = _c[1];
|
|
2238
2361
|
var query = new URLSearchParams();
|
|
@@ -2243,12 +2366,12 @@ describe('service worker host', function () {
|
|
|
2243
2366
|
query.set(key, String(value));
|
|
2244
2367
|
}
|
|
2245
2368
|
}
|
|
2246
|
-
catch (
|
|
2369
|
+
catch (e_5_1) { e_5 = { error: e_5_1 }; }
|
|
2247
2370
|
finally {
|
|
2248
2371
|
try {
|
|
2249
2372
|
if (_e && !_e.done && (_a = _d.return)) _a.call(_d);
|
|
2250
2373
|
}
|
|
2251
|
-
finally { if (
|
|
2374
|
+
finally { if (e_5) throw e_5.error; }
|
|
2252
2375
|
}
|
|
2253
2376
|
var suffix = query.toString() ? "?".concat(query) : '';
|
|
2254
2377
|
var init = { method: method };
|
|
@@ -2279,8 +2402,8 @@ describe('service worker host', function () {
|
|
|
2279
2402
|
* does.
|
|
2280
2403
|
*/
|
|
2281
2404
|
var seedDrive = function (runtime, testCase) { return __awaiter(void 0, void 0, void 0, function () {
|
|
2282
|
-
var text, _a, _b, _c, file, contents, _d, _e, _f, file, contents,
|
|
2283
|
-
var
|
|
2405
|
+
var text, _a, _b, _c, file, contents, _d, _e, _f, file, contents, e_6_1;
|
|
2406
|
+
var e_7, _g, e_6, _h;
|
|
2284
2407
|
return __generator(this, function (_j) {
|
|
2285
2408
|
switch (_j.label) {
|
|
2286
2409
|
case 0:
|
|
@@ -2292,12 +2415,12 @@ describe('service worker host', function () {
|
|
|
2292
2415
|
text["/sasjs/drive/files".concat(file)] = contents;
|
|
2293
2416
|
}
|
|
2294
2417
|
}
|
|
2295
|
-
catch (
|
|
2418
|
+
catch (e_7_1) { e_7 = { error: e_7_1 }; }
|
|
2296
2419
|
finally {
|
|
2297
2420
|
try {
|
|
2298
2421
|
if (_b && !_b.done && (_g = _a.return)) _g.call(_a);
|
|
2299
2422
|
}
|
|
2300
|
-
finally { if (
|
|
2423
|
+
finally { if (e_7) throw e_7.error; }
|
|
2301
2424
|
}
|
|
2302
2425
|
return [4 /*yield*/, runtime.load(text)];
|
|
2303
2426
|
case 1:
|
|
@@ -2320,14 +2443,14 @@ describe('service worker host', function () {
|
|
|
2320
2443
|
return [3 /*break*/, 3];
|
|
2321
2444
|
case 6: return [3 /*break*/, 9];
|
|
2322
2445
|
case 7:
|
|
2323
|
-
|
|
2324
|
-
|
|
2446
|
+
e_6_1 = _j.sent();
|
|
2447
|
+
e_6 = { error: e_6_1 };
|
|
2325
2448
|
return [3 /*break*/, 9];
|
|
2326
2449
|
case 8:
|
|
2327
2450
|
try {
|
|
2328
2451
|
if (_e && !_e.done && (_h = _d.return)) _h.call(_d);
|
|
2329
2452
|
}
|
|
2330
|
-
finally { if (
|
|
2453
|
+
finally { if (e_6) throw e_6.error; }
|
|
2331
2454
|
return [7 /*endfinally*/];
|
|
2332
2455
|
case 9: return [2 /*return*/];
|
|
2333
2456
|
}
|
|
@@ -2395,12 +2518,12 @@ describe('service worker host', function () {
|
|
|
2395
2518
|
_loop_1(testCase);
|
|
2396
2519
|
}
|
|
2397
2520
|
}
|
|
2398
|
-
catch (
|
|
2521
|
+
catch (e_4_1) { e_4 = { error: e_4_1 }; }
|
|
2399
2522
|
finally {
|
|
2400
2523
|
try {
|
|
2401
2524
|
if (DRIVE_CORPUS_1_1 && !DRIVE_CORPUS_1_1.done && (_a = DRIVE_CORPUS_1.return)) _a.call(DRIVE_CORPUS_1);
|
|
2402
2525
|
}
|
|
2403
|
-
finally { if (
|
|
2526
|
+
finally { if (e_4) throw e_4.error; }
|
|
2404
2527
|
}
|
|
2405
2528
|
});
|
|
2406
2529
|
});
|
|
@@ -323,7 +323,8 @@ function printHelpText() {
|
|
|
323
323
|
name: 'lint',
|
|
324
324
|
title: 'lint',
|
|
325
325
|
description: [
|
|
326
|
-
"Provides the capability to identify, for SAS file, whether there are any ERRORs or WARNINGs present and if so, which line number they are on."
|
|
326
|
+
"Provides the capability to identify, for SAS file, whether there are any ERRORs or WARNINGs present and if so, which line number they are on.",
|
|
327
|
+
"[2spaces]command example: sasjs lint --fail-on warning"
|
|
327
328
|
]
|
|
328
329
|
},
|
|
329
330
|
{
|
|
@@ -50,6 +50,31 @@ var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
|
50
50
|
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
51
51
|
}
|
|
52
52
|
};
|
|
53
|
+
var __read = (this && this.__read) || function (o, n) {
|
|
54
|
+
var m = typeof Symbol === "function" && o[Symbol.iterator];
|
|
55
|
+
if (!m) return o;
|
|
56
|
+
var i = m.call(o), r, ar = [], e;
|
|
57
|
+
try {
|
|
58
|
+
while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
|
|
59
|
+
}
|
|
60
|
+
catch (error) { e = { error: error }; }
|
|
61
|
+
finally {
|
|
62
|
+
try {
|
|
63
|
+
if (r && !r.done && (m = i["return"])) m.call(i);
|
|
64
|
+
}
|
|
65
|
+
finally { if (e) throw e.error; }
|
|
66
|
+
}
|
|
67
|
+
return ar;
|
|
68
|
+
};
|
|
69
|
+
var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
|
|
70
|
+
if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
|
|
71
|
+
if (ar || !(i in from)) {
|
|
72
|
+
if (!ar) ar = Array.prototype.slice.call(from, 0, i);
|
|
73
|
+
ar[i] = from[i];
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return to.concat(ar || Array.prototype.slice.call(from));
|
|
77
|
+
};
|
|
53
78
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
54
79
|
exports.LintCommand = void 0;
|
|
55
80
|
var initLint_1 = require("./initLint");
|
|
@@ -57,15 +82,35 @@ var processLint_1 = require("./processLint");
|
|
|
57
82
|
var command_1 = require("../../types/command");
|
|
58
83
|
var types_1 = require("../../types");
|
|
59
84
|
var utils_1 = require("../../utils");
|
|
85
|
+
var subCommands = ['init', 'fix', 'find'];
|
|
86
|
+
var failOnValues = ['error', 'warning'];
|
|
60
87
|
var syntax = 'lint <subCommand> [filterStrings..]';
|
|
61
|
-
var usage = 'sasjs lint [ find | fix | init ]';
|
|
88
|
+
var usage = 'sasjs lint [ find | fix | init ] [--fail-on error|warning]';
|
|
62
89
|
var description = "Provides the capability to identify, for SAS file, whether there are any ERRORs or WARNINGs present and if so, which line number they are on.";
|
|
90
|
+
/**
|
|
91
|
+
* Inserts the default `find` subcommand unless the first argument is already a
|
|
92
|
+
* subcommand, so `sasjs lint --fail-on warning` and `sasjs lint src/app.sas`
|
|
93
|
+
* both report rather than fail to parse. The check is made on the arguments
|
|
94
|
+
* rather than on their number, because an option and its value count as
|
|
95
|
+
* arguments and used to push the subcommand out of position.
|
|
96
|
+
*/
|
|
97
|
+
var withDefaultSubCommand = function (args) {
|
|
98
|
+
var firstPositional = args.slice(3).find(function (arg) { return !arg.startsWith('-'); });
|
|
99
|
+
return firstPositional && subCommands.includes(firstPositional)
|
|
100
|
+
? args
|
|
101
|
+
: __spreadArray(__spreadArray(__spreadArray([], __read(args.slice(0, 3)), false), ['find'], false), __read(args.slice(3)), false);
|
|
102
|
+
};
|
|
63
103
|
var examples = [
|
|
64
104
|
{
|
|
65
105
|
command: 'sasjs lint',
|
|
66
106
|
description: 'Lints all .sas files in the current project.\n' +
|
|
67
107
|
'All the exceptions (along with line / column numbers) will be shown in the console.'
|
|
68
108
|
},
|
|
109
|
+
{
|
|
110
|
+
command: 'sasjs lint --fail-on warning',
|
|
111
|
+
description: 'Lints all .sas files in the current project and fails on a warning as well as an error.\n' +
|
|
112
|
+
'The same can be set for every run by adding `"default": "error"` to `severityLevel` in `.sasjslint`.'
|
|
113
|
+
},
|
|
69
114
|
{
|
|
70
115
|
command: 'sasjs lint find mf_a mf_b',
|
|
71
116
|
description: 'Lints all .sas files starting from mf_a or mf_b in the current project.\n' +
|
|
@@ -109,23 +154,32 @@ var examples = [
|
|
|
109
154
|
var LintCommand = /** @class */ (function (_super) {
|
|
110
155
|
__extends(LintCommand, _super);
|
|
111
156
|
function LintCommand(args) {
|
|
112
|
-
|
|
113
|
-
args.splice(3, 0, 'find');
|
|
114
|
-
}
|
|
115
|
-
return _super.call(this, args, {
|
|
157
|
+
return _super.call(this, withDefaultSubCommand(args), {
|
|
116
158
|
syntax: syntax,
|
|
117
159
|
usage: usage,
|
|
118
160
|
description: description,
|
|
119
|
-
examples: examples
|
|
161
|
+
examples: examples,
|
|
162
|
+
parseOptions: {
|
|
163
|
+
'fail-on': {
|
|
164
|
+
type: 'string',
|
|
165
|
+
description: "Lowest severity that fails the command: ".concat(failOnValues.join(', '), ". Defaults to error, so a warning is reported without failing. Applies to the report run, not to lint fix.")
|
|
166
|
+
}
|
|
167
|
+
}
|
|
120
168
|
}) || this;
|
|
121
169
|
}
|
|
122
170
|
LintCommand.prototype.execute = function () {
|
|
123
171
|
return __awaiter(this, void 0, void 0, function () {
|
|
124
|
-
var _a, subCommand, filterStrings;
|
|
125
|
-
|
|
126
|
-
|
|
172
|
+
var _a, subCommand, filterStrings, failOn, failOnSeverity;
|
|
173
|
+
var _b;
|
|
174
|
+
return __generator(this, function (_c) {
|
|
175
|
+
switch (_c.label) {
|
|
127
176
|
case 0:
|
|
128
|
-
_a = this.parsed, subCommand = _a.subCommand, filterStrings = _a.filterStrings;
|
|
177
|
+
_a = this.parsed, subCommand = _a.subCommand, filterStrings = _a.filterStrings, failOn = _a.failOn;
|
|
178
|
+
if (failOn !== undefined && !failOnValues.includes(failOn)) {
|
|
179
|
+
(0, utils_1.displayError)(new Error("Invalid value for --fail-on: ".concat(failOn, ". Use one of ").concat(failOnValues.join(', '), ".")));
|
|
180
|
+
return [2 /*return*/, command_1.ReturnCode.InvalidCommand];
|
|
181
|
+
}
|
|
182
|
+
failOnSeverity = (_b = failOn) !== null && _b !== void 0 ? _b : 'error';
|
|
129
183
|
if (!(subCommand === 'init')) return [3 /*break*/, 2];
|
|
130
184
|
return [4 /*yield*/, (0, initLint_1.initLint)()
|
|
131
185
|
.then(function (res) {
|
|
@@ -139,7 +193,7 @@ var LintCommand = /** @class */ (function (_super) {
|
|
|
139
193
|
(0, utils_1.displayError)(err, 'An error has occurred whilst initialising SASjs Lint.');
|
|
140
194
|
return command_1.ReturnCode.InternalError;
|
|
141
195
|
})];
|
|
142
|
-
case 1: return [2 /*return*/,
|
|
196
|
+
case 1: return [2 /*return*/, _c.sent()];
|
|
143
197
|
case 2:
|
|
144
198
|
if (!(subCommand === 'fix')) return [3 /*break*/, 4];
|
|
145
199
|
return [4 /*yield*/, (0, processLint_1.lintFix)(filterStrings)
|
|
@@ -148,13 +202,20 @@ var LintCommand = /** @class */ (function (_super) {
|
|
|
148
202
|
(0, utils_1.displayError)(err, 'An error has occurred while running SASjs Lint Fix.');
|
|
149
203
|
return command_1.ReturnCode.LintError;
|
|
150
204
|
})];
|
|
151
|
-
case 3: return [2 /*return*/,
|
|
205
|
+
case 3: return [2 /*return*/, _c.sent()];
|
|
152
206
|
case 4: return [4 /*yield*/, (0, processLint_1.processLint)(filterStrings)
|
|
153
207
|
.then(function (result) {
|
|
154
208
|
if (result.errors) {
|
|
155
209
|
(0, utils_1.displayError)('Please fix the identified lint errors.');
|
|
156
210
|
return command_1.ReturnCode.LintError;
|
|
157
211
|
}
|
|
212
|
+
if (result.warnings && failOnSeverity === 'warning') {
|
|
213
|
+
var count = result.warningCount;
|
|
214
|
+
(0, utils_1.displayError)(count === 1
|
|
215
|
+
? '1 lint warning treated as an error because of --fail-on warning. Please fix it.'
|
|
216
|
+
: "".concat(count, " lint warnings treated as errors because of --fail-on warning. Please fix them."));
|
|
217
|
+
return command_1.ReturnCode.LintError;
|
|
218
|
+
}
|
|
158
219
|
if (result.warnings) {
|
|
159
220
|
process.logger.warn('Please fix the identified lint warnings.');
|
|
160
221
|
return command_1.ReturnCode.Success;
|
|
@@ -166,7 +227,7 @@ var LintCommand = /** @class */ (function (_super) {
|
|
|
166
227
|
(0, utils_1.displayError)(err, 'An error has occurred when processing lint operation.');
|
|
167
228
|
return command_1.ReturnCode.InternalError;
|
|
168
229
|
})];
|
|
169
|
-
case 5: return [2 /*return*/,
|
|
230
|
+
case 5: return [2 /*return*/, _c.sent()];
|
|
170
231
|
}
|
|
171
232
|
});
|
|
172
233
|
});
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
interface LintResult {
|
|
2
2
|
warnings: boolean;
|
|
3
3
|
errors: boolean;
|
|
4
|
+
/** Number of WARNING diagnostics found, across every linted file. */
|
|
5
|
+
warningCount: number;
|
|
6
|
+
/** Number of ERROR diagnostics found, across every linted file. */
|
|
7
|
+
errorCount: number;
|
|
4
8
|
}
|
|
5
9
|
/**
|
|
6
10
|
* Fixes lint violations in all .sas files in the current project if no filterArray is provided
|
|
@@ -70,6 +70,12 @@ var path_1 = __importDefault(require("path"));
|
|
|
70
70
|
var chalk_1 = __importDefault(require("chalk"));
|
|
71
71
|
var lint_1 = require("@sasjs/lint");
|
|
72
72
|
var utils_1 = require("@sasjs/utils");
|
|
73
|
+
/**
|
|
74
|
+
* `sasjs lint` is documented to work without a `sasjsconfig.json`, and loading
|
|
75
|
+
* that file is the only thing that sets `process.projectDir` - so a filter
|
|
76
|
+
* string is resolved against the current directory when no project was loaded.
|
|
77
|
+
*/
|
|
78
|
+
var getProjectDir = function () { return process.projectDir || process.cwd(); };
|
|
73
79
|
/**
|
|
74
80
|
* Fixes lint violations in all .sas files in the current project if no filterArray is provided
|
|
75
81
|
* Otherwise fixes lint violations of only those .sas files which matches filter array
|
|
@@ -125,7 +131,7 @@ function lintFix() {
|
|
|
125
131
|
.map(function (pattern) { return "(".concat(pattern, ")"); })
|
|
126
132
|
.join('|');
|
|
127
133
|
regex_1 = new RegExp(regexPattern);
|
|
128
|
-
return [4 /*yield*/, (0, utils_1.listSasFilesInFolder)(
|
|
134
|
+
return [4 /*yield*/, (0, utils_1.listSasFilesInFolder)(getProjectDir(), true, [
|
|
129
135
|
'node_modules'
|
|
130
136
|
])];
|
|
131
137
|
case 2:
|
|
@@ -194,7 +200,12 @@ function processLint() {
|
|
|
194
200
|
return __generator(this, function (_a) {
|
|
195
201
|
switch (_a.label) {
|
|
196
202
|
case 0:
|
|
197
|
-
found = {
|
|
203
|
+
found = {
|
|
204
|
+
warnings: false,
|
|
205
|
+
errors: false,
|
|
206
|
+
warningCount: 0,
|
|
207
|
+
errorCount: 0
|
|
208
|
+
};
|
|
198
209
|
sasjsDiagnosticsMap = new Map();
|
|
199
210
|
if (!filterArray.length) return [3 /*break*/, 5];
|
|
200
211
|
absoluteFilePaths = filterArray.filter(function (pattern) {
|
|
@@ -226,7 +237,7 @@ function processLint() {
|
|
|
226
237
|
.map(function (pattern) { return "(".concat(pattern, ")"); })
|
|
227
238
|
.join('|');
|
|
228
239
|
regex_2 = new RegExp(regexPattern);
|
|
229
|
-
return [4 /*yield*/, (0, utils_1.listSasFilesInFolder)(
|
|
240
|
+
return [4 /*yield*/, (0, utils_1.listSasFilesInFolder)(getProjectDir(), true, [
|
|
230
241
|
'node_modules'
|
|
231
242
|
])];
|
|
232
243
|
case 2:
|
|
@@ -237,7 +248,7 @@ function processLint() {
|
|
|
237
248
|
return __generator(this, function (_d) {
|
|
238
249
|
switch (_d.label) {
|
|
239
250
|
case 0:
|
|
240
|
-
filePath = path_1.default.join(
|
|
251
|
+
filePath = path_1.default.join(getProjectDir(), file);
|
|
241
252
|
_b = (_a = sasjsDiagnosticsMap).set;
|
|
242
253
|
_c = [filePath];
|
|
243
254
|
return [4 /*yield*/, (0, lint_1.lintFile)(filePath)];
|
|
@@ -258,12 +269,10 @@ function processLint() {
|
|
|
258
269
|
_a.label = 7;
|
|
259
270
|
case 7:
|
|
260
271
|
sasjsDiagnosticsMap.forEach(function (sasjsDiagnostics, filePath) {
|
|
261
|
-
found.
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
found.errors =
|
|
265
|
-
found.errors ||
|
|
266
|
-
!!sasjsDiagnostics.find(function (d) { return d.severity === lint_1.Severity.Error; });
|
|
272
|
+
found.warningCount += sasjsDiagnostics.filter(function (d) { return d.severity === lint_1.Severity.Warning; }).length;
|
|
273
|
+
found.errorCount += sasjsDiagnostics.filter(function (d) { return d.severity === lint_1.Severity.Error; }).length;
|
|
274
|
+
found.warnings = found.warnings || found.warningCount > 0;
|
|
275
|
+
found.errors = found.errors || found.errorCount > 0;
|
|
267
276
|
if (sasjsDiagnostics.length) {
|
|
268
277
|
displayDiagnostics(filePath, sasjsDiagnostics);
|
|
269
278
|
}
|
|
@@ -138,10 +138,13 @@ describe('LintCommand', function () {
|
|
|
138
138
|
switch (_a.label) {
|
|
139
139
|
case 0:
|
|
140
140
|
args = __spreadArray(__spreadArray([], __read(defaultArgs), false), ['lint'], false);
|
|
141
|
-
jest
|
|
142
|
-
.
|
|
143
|
-
|
|
144
|
-
|
|
141
|
+
jest.spyOn(processModule, 'processLint').mockImplementation(function () {
|
|
142
|
+
return Promise.resolve({
|
|
143
|
+
warnings: true,
|
|
144
|
+
errors: false,
|
|
145
|
+
warningCount: 3,
|
|
146
|
+
errorCount: 0
|
|
147
|
+
});
|
|
145
148
|
});
|
|
146
149
|
command = new lintCommand_1.LintCommand(args);
|
|
147
150
|
return [4 /*yield*/, command.execute()];
|
|
@@ -178,10 +181,13 @@ describe('LintCommand', function () {
|
|
|
178
181
|
switch (_a.label) {
|
|
179
182
|
case 0:
|
|
180
183
|
args = __spreadArray(__spreadArray([], __read(defaultArgs), false), ['lint'], false);
|
|
181
|
-
jest
|
|
182
|
-
.
|
|
183
|
-
|
|
184
|
-
|
|
184
|
+
jest.spyOn(processModule, 'processLint').mockImplementation(function () {
|
|
185
|
+
return Promise.resolve({
|
|
186
|
+
warnings: false,
|
|
187
|
+
errors: true,
|
|
188
|
+
warningCount: 0,
|
|
189
|
+
errorCount: 1
|
|
190
|
+
});
|
|
185
191
|
});
|
|
186
192
|
command = new lintCommand_1.LintCommand(args);
|
|
187
193
|
return [4 /*yield*/, command.execute()];
|
|
@@ -193,6 +199,111 @@ describe('LintCommand', function () {
|
|
|
193
199
|
}
|
|
194
200
|
});
|
|
195
201
|
}); });
|
|
202
|
+
it('should default the subcommand when --fail-on comes before the filter', function () {
|
|
203
|
+
var args = __spreadArray(__spreadArray([], __read(defaultArgs), false), ['lint', '--fail-on', 'warning', 'appinit'], false);
|
|
204
|
+
var command = new lintCommand_1.LintCommand(args);
|
|
205
|
+
expect(command.name).toEqual('lint');
|
|
206
|
+
expect(command.subCommand).toEqual('find');
|
|
207
|
+
});
|
|
208
|
+
it('should default the subcommand when only --fail-on is given', function () {
|
|
209
|
+
var args = __spreadArray(__spreadArray([], __read(defaultArgs), false), ['lint', '--fail-on=warning'], false);
|
|
210
|
+
var command = new lintCommand_1.LintCommand(args);
|
|
211
|
+
expect(command.name).toEqual('lint');
|
|
212
|
+
expect(command.subCommand).toEqual('find');
|
|
213
|
+
});
|
|
214
|
+
it('should keep an explicit subcommand ahead of --fail-on', function () {
|
|
215
|
+
var args = __spreadArray(__spreadArray([], __read(defaultArgs), false), ['lint', 'find', '--fail-on', 'warning'], false);
|
|
216
|
+
var command = new lintCommand_1.LintCommand(args);
|
|
217
|
+
expect(command.subCommand).toEqual('find');
|
|
218
|
+
});
|
|
219
|
+
it('should fail on warnings when --fail-on warning is passed', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
220
|
+
var args, command, returnCode;
|
|
221
|
+
return __generator(this, function (_a) {
|
|
222
|
+
switch (_a.label) {
|
|
223
|
+
case 0:
|
|
224
|
+
args = __spreadArray(__spreadArray([], __read(defaultArgs), false), ['lint', '--fail-on', 'warning'], false);
|
|
225
|
+
jest.spyOn(processModule, 'processLint').mockImplementation(function () {
|
|
226
|
+
return Promise.resolve({
|
|
227
|
+
warnings: true,
|
|
228
|
+
errors: false,
|
|
229
|
+
warningCount: 2,
|
|
230
|
+
errorCount: 0
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
command = new lintCommand_1.LintCommand(args);
|
|
234
|
+
return [4 /*yield*/, command.execute()];
|
|
235
|
+
case 1:
|
|
236
|
+
returnCode = _a.sent();
|
|
237
|
+
expect(returnCode).toEqual(command_1.ReturnCode.LintError);
|
|
238
|
+
expect(process.logger.error).toHaveBeenCalledWith('', '2 lint warnings treated as errors because of --fail-on warning. Please fix them.');
|
|
239
|
+
return [2 /*return*/];
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
}); });
|
|
243
|
+
it('should word a single warning as one error', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
244
|
+
var args, command, returnCode;
|
|
245
|
+
return __generator(this, function (_a) {
|
|
246
|
+
switch (_a.label) {
|
|
247
|
+
case 0:
|
|
248
|
+
args = __spreadArray(__spreadArray([], __read(defaultArgs), false), ['lint', '--fail-on', 'warning'], false);
|
|
249
|
+
jest.spyOn(processModule, 'processLint').mockImplementation(function () {
|
|
250
|
+
return Promise.resolve({
|
|
251
|
+
warnings: true,
|
|
252
|
+
errors: false,
|
|
253
|
+
warningCount: 1,
|
|
254
|
+
errorCount: 0
|
|
255
|
+
});
|
|
256
|
+
});
|
|
257
|
+
command = new lintCommand_1.LintCommand(args);
|
|
258
|
+
return [4 /*yield*/, command.execute()];
|
|
259
|
+
case 1:
|
|
260
|
+
returnCode = _a.sent();
|
|
261
|
+
expect(returnCode).toEqual(command_1.ReturnCode.LintError);
|
|
262
|
+
expect(process.logger.error).toHaveBeenCalledWith('', '1 lint warning treated as an error because of --fail-on warning. Please fix it.');
|
|
263
|
+
return [2 /*return*/];
|
|
264
|
+
}
|
|
265
|
+
});
|
|
266
|
+
}); });
|
|
267
|
+
it('should not fail on warnings when --fail-on error is passed', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
268
|
+
var args, command, returnCode;
|
|
269
|
+
return __generator(this, function (_a) {
|
|
270
|
+
switch (_a.label) {
|
|
271
|
+
case 0:
|
|
272
|
+
args = __spreadArray(__spreadArray([], __read(defaultArgs), false), ['lint', '--fail-on', 'error'], false);
|
|
273
|
+
jest.spyOn(processModule, 'processLint').mockImplementation(function () {
|
|
274
|
+
return Promise.resolve({
|
|
275
|
+
warnings: true,
|
|
276
|
+
errors: false,
|
|
277
|
+
warningCount: 2,
|
|
278
|
+
errorCount: 0
|
|
279
|
+
});
|
|
280
|
+
});
|
|
281
|
+
command = new lintCommand_1.LintCommand(args);
|
|
282
|
+
return [4 /*yield*/, command.execute()];
|
|
283
|
+
case 1:
|
|
284
|
+
returnCode = _a.sent();
|
|
285
|
+
expect(returnCode).toEqual(command_1.ReturnCode.Success);
|
|
286
|
+
expect(process.logger.warn).toHaveBeenCalled();
|
|
287
|
+
return [2 /*return*/];
|
|
288
|
+
}
|
|
289
|
+
});
|
|
290
|
+
}); });
|
|
291
|
+
it('should reject an unknown --fail-on value', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
292
|
+
var args, command, returnCode;
|
|
293
|
+
return __generator(this, function (_a) {
|
|
294
|
+
switch (_a.label) {
|
|
295
|
+
case 0:
|
|
296
|
+
args = __spreadArray(__spreadArray([], __read(defaultArgs), false), ['lint', '--fail-on', 'note'], false);
|
|
297
|
+
command = new lintCommand_1.LintCommand(args);
|
|
298
|
+
return [4 /*yield*/, command.execute()];
|
|
299
|
+
case 1:
|
|
300
|
+
returnCode = _a.sent();
|
|
301
|
+
expect(returnCode).toEqual(command_1.ReturnCode.InvalidCommand);
|
|
302
|
+
expect(processModule.processLint).not.toHaveBeenCalled();
|
|
303
|
+
return [2 /*return*/];
|
|
304
|
+
}
|
|
305
|
+
});
|
|
306
|
+
}); });
|
|
196
307
|
});
|
|
197
308
|
describe('LintCommand - fix', function () {
|
|
198
309
|
beforeEach(function () {
|
|
@@ -295,10 +406,13 @@ var setupMocks = function () {
|
|
|
295
406
|
jest.resetAllMocks();
|
|
296
407
|
jest.mock('../processLint');
|
|
297
408
|
jest.mock('../initLint');
|
|
298
|
-
jest
|
|
299
|
-
.
|
|
300
|
-
|
|
301
|
-
|
|
409
|
+
jest.spyOn(processModule, 'processLint').mockImplementation(function () {
|
|
410
|
+
return Promise.resolve({
|
|
411
|
+
warnings: false,
|
|
412
|
+
errors: false,
|
|
413
|
+
warningCount: 0,
|
|
414
|
+
errorCount: 0
|
|
415
|
+
});
|
|
302
416
|
});
|
|
303
417
|
jest
|
|
304
418
|
.spyOn(processModule, 'lintFix')
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,127 @@
|
|
|
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 os_1 = __importDefault(require("os"));
|
|
44
|
+
var path_1 = __importDefault(require("path"));
|
|
45
|
+
var processLint_1 = require("../processLint");
|
|
46
|
+
/**
|
|
47
|
+
* `sasjs lint` is documented to run without a `sasjsconfig.json`, and loading
|
|
48
|
+
* that file is the only thing that sets `process.projectDir` - so these cases
|
|
49
|
+
* run with it empty, the way a project directory without a config does.
|
|
50
|
+
*/
|
|
51
|
+
describe('processLint', function () {
|
|
52
|
+
var originalCwd = process.cwd();
|
|
53
|
+
var tmpDir;
|
|
54
|
+
beforeEach(function () {
|
|
55
|
+
tmpDir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'sasjs-lint-'));
|
|
56
|
+
fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'trailing.sas'), "%put 'hello'; \n");
|
|
57
|
+
process.chdir(tmpDir);
|
|
58
|
+
process.projectDir = '';
|
|
59
|
+
});
|
|
60
|
+
afterEach(function () {
|
|
61
|
+
process.chdir(originalCwd);
|
|
62
|
+
process.projectDir = '';
|
|
63
|
+
fs_1.default.rmSync(tmpDir, { recursive: true, force: true });
|
|
64
|
+
});
|
|
65
|
+
it('should resolve a filter string against the current directory when no project is loaded', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
66
|
+
var result;
|
|
67
|
+
return __generator(this, function (_a) {
|
|
68
|
+
switch (_a.label) {
|
|
69
|
+
case 0: return [4 /*yield*/, (0, processLint_1.processLint)(['trailing'])];
|
|
70
|
+
case 1:
|
|
71
|
+
result = _a.sent();
|
|
72
|
+
expect(result.warnings).toEqual(true);
|
|
73
|
+
expect(result.warningCount).toBeGreaterThan(0);
|
|
74
|
+
expect(result.errors).toEqual(false);
|
|
75
|
+
expect(result.errorCount).toEqual(0);
|
|
76
|
+
return [2 /*return*/];
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
}); });
|
|
80
|
+
it('should still accept an absolute path when no project is loaded', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
81
|
+
var result;
|
|
82
|
+
return __generator(this, function (_a) {
|
|
83
|
+
switch (_a.label) {
|
|
84
|
+
case 0: return [4 /*yield*/, (0, processLint_1.processLint)([path_1.default.join(tmpDir, 'trailing.sas')])];
|
|
85
|
+
case 1:
|
|
86
|
+
result = _a.sent();
|
|
87
|
+
expect(result.warningCount).toBeGreaterThan(0);
|
|
88
|
+
return [2 /*return*/];
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
}); });
|
|
92
|
+
it('should report no findings when the filter matches nothing', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
93
|
+
var result;
|
|
94
|
+
return __generator(this, function (_a) {
|
|
95
|
+
switch (_a.label) {
|
|
96
|
+
case 0: return [4 /*yield*/, (0, processLint_1.processLint)(['no-such-file'])];
|
|
97
|
+
case 1:
|
|
98
|
+
result = _a.sent();
|
|
99
|
+
expect(result).toEqual({
|
|
100
|
+
warnings: false,
|
|
101
|
+
errors: false,
|
|
102
|
+
warningCount: 0,
|
|
103
|
+
errorCount: 0
|
|
104
|
+
});
|
|
105
|
+
return [2 /*return*/];
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
}); });
|
|
109
|
+
it('should count errors separately from warnings', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
110
|
+
var result;
|
|
111
|
+
return __generator(this, function (_a) {
|
|
112
|
+
switch (_a.label) {
|
|
113
|
+
case 0:
|
|
114
|
+
fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'password.sas'), "%put '{SASENC}'; \n");
|
|
115
|
+
return [4 /*yield*/, (0, processLint_1.processLint)(['password'])];
|
|
116
|
+
case 1:
|
|
117
|
+
result = _a.sent();
|
|
118
|
+
expect(result.errorCount).toBe(1);
|
|
119
|
+
expect(result.errors).toEqual(true);
|
|
120
|
+
// The trailing spaces and the missing header are both warnings, so the
|
|
121
|
+
// counts stay independent of the error.
|
|
122
|
+
expect(result.warningCount).toBeGreaterThanOrEqual(1);
|
|
123
|
+
return [2 /*return*/];
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
}); });
|
|
127
|
+
});
|
package/package.json
CHANGED
|
@@ -73,7 +73,7 @@
|
|
|
73
73
|
"@sasjs/adapter": "5.1.3",
|
|
74
74
|
"@sasjs/api": "0.23.0",
|
|
75
75
|
"@sasjs/core": "6.1.1",
|
|
76
|
-
"@sasjs/lint": "5.0
|
|
76
|
+
"@sasjs/lint": "^5.1.0",
|
|
77
77
|
"@sasjs/utils": "4.9.0",
|
|
78
78
|
"axios": "1.20.0",
|
|
79
79
|
"chalk": "4.1.2",
|
|
@@ -125,5 +125,5 @@
|
|
|
125
125
|
"ws": "8.21.0",
|
|
126
126
|
"nanoid": "3.3.18"
|
|
127
127
|
},
|
|
128
|
-
"version": "6.
|
|
128
|
+
"version": "6.21.0"
|
|
129
129
|
}
|