@sasjs/cli 6.9.0 → 6.10.1
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 +1 -6
- package/build/commands/build/internal/mockSite/assets/runtime-core.js +23 -1
- package/build/commands/build/internal/mockSite/assets/sw.js +14 -0
- package/build/commands/build/internal/mockSite/spec/runtime-core.spec.js +10 -6
- package/build/commands/build/internal/mockSite/spec/sw.spec.js +86 -0
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,11 +1,6 @@
|
|
|
1
1
|
# SASjs CLI
|
|
2
2
|
|
|
3
|
-
[![npm package][npm-image]][npm-url]
|
|
4
|
-
[![Github Workflow][githubworkflow-image]][githubworkflow-url]
|
|
5
|
-
[](<>)
|
|
6
|
-

|
|
7
|
-
[](https://github.com/sasjs/cli/issues?q=is%3Aissue+is%3Aclosed)
|
|
8
|
-
[](https://github.com/sasjs/cli/issues)
|
|
3
|
+
[![npm package][npm-image]][npm-url] [![Github Workflow][githubworkflow-image]][githubworkflow-url] [](https://scorecard.dev/viewer/?uri=github.com/sasjs/cli) [](<>)  [](https://github.com/sasjs/cli/issues?q=is%3Aissue+is%3Aclosed) [](https://github.com/sasjs/cli/issues)
|
|
9
4
|
|
|
10
5
|
[npm-image]: https://img.shields.io/npm/v/@sasjs/cli.svg
|
|
11
6
|
[npm-url]: http://npmjs.org/package/@sasjs/cli
|
|
@@ -954,6 +954,25 @@ const resolveProgram = (program) => {
|
|
|
954
954
|
return null
|
|
955
955
|
}
|
|
956
956
|
|
|
957
|
+
/**
|
|
958
|
+
* The header file a session starts with. sasjs/server seeds this exact line
|
|
959
|
+
* into a JS session (controllers/internal/Session.ts), so a service that
|
|
960
|
+
* declares no type answers text/html rather than leaving the choice to the
|
|
961
|
+
* browser - a mock build matches that here.
|
|
962
|
+
*
|
|
963
|
+
* The trailing newline is part of it: the server writes it for a JS session
|
|
964
|
+
* (`'content-type: text/html; charset=utf-8\n'` in Session.ts, as against the
|
|
965
|
+
* SAS 9 session's seed with no newline), and it is what keeps a service that
|
|
966
|
+
* appends a header without a leading newline on its own line - otherwise the two
|
|
967
|
+
* concatenate into one unusable value.
|
|
968
|
+
*
|
|
969
|
+
* It is a default, not a declaration: the worker ignores it for a byte body,
|
|
970
|
+
* so a service that writes bytes and names no type is still an undeclared
|
|
971
|
+
* download rather than being labelled text/html. The worker reads the value
|
|
972
|
+
* from HEADERS_SEED below, so the seed and the rule cannot drift.
|
|
973
|
+
*/
|
|
974
|
+
const SEEDED_HEADERS = 'content-type: text/html; charset=utf-8\n'
|
|
975
|
+
|
|
957
976
|
const runProgram = ({ program, code, params = {}, tables = [] }) => {
|
|
958
977
|
const payloadCode = code
|
|
959
978
|
const codePath = program ? resolveProgram(program) : null
|
|
@@ -977,7 +996,7 @@ const runProgram = ({ program, code, params = {}, tables = [] }) => {
|
|
|
977
996
|
const headersPath = `${sessionDir}/stpsrv_header.txt`
|
|
978
997
|
const tokenFile = `${sessionDir}/reqHeaders.txt`
|
|
979
998
|
FILES.set(weboutPath, '')
|
|
980
|
-
FILES.set(headersPath,
|
|
999
|
+
FILES.set(headersPath, SEEDED_HEADERS)
|
|
981
1000
|
FILES.set(tokenFile, '')
|
|
982
1001
|
|
|
983
1002
|
const varStatements = Object.entries(params)
|
|
@@ -1338,6 +1357,9 @@ const api = {
|
|
|
1338
1357
|
list: (folder) => listFolder(folder),
|
|
1339
1358
|
read: (file) => readDriveFile(file),
|
|
1340
1359
|
tree: () => buildTree(),
|
|
1360
|
+
// the content type a session's header file is seeded with, so the worker can
|
|
1361
|
+
// tell a default from a declaration rather than carrying a second literal
|
|
1362
|
+
HEADERS_SEED: SEEDED_HEADERS,
|
|
1341
1363
|
// the format the snapshot download stamps, so persist.js writes the number
|
|
1342
1364
|
// this runtime reads rather than a second literal
|
|
1343
1365
|
DRIVE_FORMAT
|
|
@@ -101,6 +101,14 @@ const parseHeaders = (content) => {
|
|
|
101
101
|
return headers
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
+
/**
|
|
105
|
+
* The content type a session's header file is seeded with - the server's
|
|
106
|
+
* default, not something the service asked for. The runtime owns the seed line,
|
|
107
|
+
* so the value is taken from it rather than repeated here: a byte body that
|
|
108
|
+
* carries only this type is an undeclared download, not a text/html one.
|
|
109
|
+
*/
|
|
110
|
+
const SEEDED_HEADERS = String(R.HEADERS_SEED)
|
|
111
|
+
|
|
104
112
|
/**
|
|
105
113
|
* The reply a run produces: the webout, in the shape and under the name the
|
|
106
114
|
* service asked for. A debug reply is the webout framed with the log, so it
|
|
@@ -114,6 +122,12 @@ const weboutResponse = (result, debug) => {
|
|
|
114
122
|
|
|
115
123
|
const headers = parseHeaders(result.headers)
|
|
116
124
|
if (result.binary) {
|
|
125
|
+
// the seed is a default, so a byte body that leaves the seeded file untouched
|
|
126
|
+
// declares nothing: it keeps the type an undeclared download gets. Compared as
|
|
127
|
+
// the raw text rather than as the parsed value, so a service that declares
|
|
128
|
+
// this same content type itself keeps it - only the untouched seed is dropped.
|
|
129
|
+
if (String(result.headers) === SEEDED_HEADERS)
|
|
130
|
+
delete headers['content-type']
|
|
117
131
|
if (!headers['content-type'])
|
|
118
132
|
headers['content-type'] = 'application/octet-stream'
|
|
119
133
|
return new Response(toBytes(result.webout), { status: 200, headers })
|
|
@@ -175,23 +175,27 @@ describe('browser runtime', function () {
|
|
|
175
175
|
expect(parsed.SYSPROCESSNAME).toEqual('Browser');
|
|
176
176
|
expect(parsed.SYSSCPL).toEqual('browser');
|
|
177
177
|
// The content type is the point: the block sets it, so the worker hands the
|
|
178
|
-
// app JSON rather than a string. The
|
|
179
|
-
//
|
|
178
|
+
// app JSON rather than a string. The session seed is text/html, so this
|
|
179
|
+
// record has to come last for the last-line-wins rule to land on JSON.
|
|
180
180
|
expect(result.headers).toContain('content-type: application/json');
|
|
181
|
+
expect(result.headers.trim().split('\n').pop()).toEqual('content-type: application/json');
|
|
181
182
|
return [2 /*return*/];
|
|
182
183
|
}
|
|
183
184
|
});
|
|
184
185
|
}); });
|
|
185
|
-
it('
|
|
186
|
+
it('seeds the headers the way a JS session does, for a service that only assigns _webout', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
186
187
|
var runtime, result;
|
|
187
188
|
return __generator(this, function (_a) {
|
|
188
189
|
runtime = loadRuntime();
|
|
189
190
|
result = runtime.run({ code: "_webout = '<h1>raw</h1>'" });
|
|
190
191
|
expect(result.error).toBeNull();
|
|
191
192
|
expect(result.webout).toEqual('<h1>raw</h1>');
|
|
192
|
-
//
|
|
193
|
-
//
|
|
194
|
-
|
|
193
|
+
// A service that declares nothing answers text/html on a server, so the
|
|
194
|
+
// mock starts from the same line rather than from an empty file. The worker
|
|
195
|
+
// treats this value as undeclared for a byte body, which is why the seed
|
|
196
|
+
// does not turn a download into text/html.
|
|
197
|
+
expect(result.headers).toEqual('content-type: text/html; charset=utf-8\n');
|
|
198
|
+
expect(runtime.HEADERS_SEED).toEqual('content-type: text/html; charset=utf-8\n');
|
|
195
199
|
return [2 /*return*/];
|
|
196
200
|
});
|
|
197
201
|
}); });
|
|
@@ -239,6 +239,92 @@ describe('service worker host', function () {
|
|
|
239
239
|
}
|
|
240
240
|
});
|
|
241
241
|
}); });
|
|
242
|
+
it('keeps a header appended without a leading newline on its own line', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
243
|
+
var dispatch, response, _a;
|
|
244
|
+
return __generator(this, function (_b) {
|
|
245
|
+
switch (_b.label) {
|
|
246
|
+
case 0:
|
|
247
|
+
dispatch = streaming([
|
|
248
|
+
"fs.appendFileSync(_SASJS_WEBOUT_HEADERS, 'Content-type: application/json')",
|
|
249
|
+
"_webout = '{}'"
|
|
250
|
+
].join('\n')).dispatch;
|
|
251
|
+
return [4 /*yield*/, dispatch(downloadPath())];
|
|
252
|
+
case 1:
|
|
253
|
+
response = _b.sent();
|
|
254
|
+
expect(response.headers.get('content-type')).toBe('application/json');
|
|
255
|
+
_a = expect;
|
|
256
|
+
return [4 /*yield*/, response.text()];
|
|
257
|
+
case 2:
|
|
258
|
+
_a.apply(void 0, [_b.sent()]).toBe('{}');
|
|
259
|
+
return [2 /*return*/];
|
|
260
|
+
}
|
|
261
|
+
});
|
|
262
|
+
}); });
|
|
263
|
+
it('keeps a declared type that happens to equal the seed', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
264
|
+
var dispatch, response;
|
|
265
|
+
return __generator(this, function (_a) {
|
|
266
|
+
switch (_a.label) {
|
|
267
|
+
case 0:
|
|
268
|
+
dispatch = streaming([
|
|
269
|
+
"fs.writeFileSync(_SASJS_WEBOUT_HEADERS, 'content-type: text/html; charset=utf-8')",
|
|
270
|
+
'fs.writeFileSync(weboutPath, Uint8Array.from([1, 2, 3]))'
|
|
271
|
+
].join('\n')).dispatch;
|
|
272
|
+
return [4 /*yield*/, dispatch(downloadPath())];
|
|
273
|
+
case 1:
|
|
274
|
+
response = _a.sent();
|
|
275
|
+
expect(response.headers.get('content-type')).toBe('text/html; charset=utf-8');
|
|
276
|
+
return [2 /*return*/];
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
}); });
|
|
280
|
+
it('answers text/html for a service that declares no type, as a server does', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
281
|
+
var dispatch, response, _a;
|
|
282
|
+
return __generator(this, function (_b) {
|
|
283
|
+
switch (_b.label) {
|
|
284
|
+
case 0:
|
|
285
|
+
dispatch = seeded().dispatch;
|
|
286
|
+
return [4 /*yield*/, dispatch('/SASjsApi/stp/execute?_program=' +
|
|
287
|
+
encodeURIComponent("".concat(appLoc, "/services/public/getdata")))
|
|
288
|
+
// The session header file is seeded with the server's line, so a service
|
|
289
|
+
// that assigns _webout and stops answers with a content type instead of
|
|
290
|
+
// leaving the choice to the browser.
|
|
291
|
+
];
|
|
292
|
+
case 1:
|
|
293
|
+
response = _b.sent();
|
|
294
|
+
// The session header file is seeded with the server's line, so a service
|
|
295
|
+
// that assigns _webout and stops answers with a content type instead of
|
|
296
|
+
// leaving the choice to the browser.
|
|
297
|
+
expect(response.headers.get('content-type')).toBe('text/html; charset=utf-8');
|
|
298
|
+
_a = expect;
|
|
299
|
+
return [4 /*yield*/, response.text()];
|
|
300
|
+
case 2:
|
|
301
|
+
_a.apply(void 0, [_b.sent()]).toBe('seeded');
|
|
302
|
+
return [2 /*return*/];
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
}); });
|
|
306
|
+
it('keeps a declared text/html on a byte body, and ignores only the seed', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
307
|
+
var dispatch, response;
|
|
308
|
+
return __generator(this, function (_a) {
|
|
309
|
+
switch (_a.label) {
|
|
310
|
+
case 0:
|
|
311
|
+
dispatch = streaming([
|
|
312
|
+
"fs.appendFileSync(_SASJS_WEBOUT_HEADERS, '\\nContent-type: text/html')",
|
|
313
|
+
'fs.writeFileSync(weboutPath, Uint8Array.from([1, 2, 3]))'
|
|
314
|
+
].join('\n')).dispatch;
|
|
315
|
+
return [4 /*yield*/, dispatch(downloadPath())
|
|
316
|
+
// only the seeded value is treated as undeclared; the same type written by
|
|
317
|
+
// the service is a declaration and is applied
|
|
318
|
+
];
|
|
319
|
+
case 1:
|
|
320
|
+
response = _a.sent();
|
|
321
|
+
// only the seeded value is treated as undeclared; the same type written by
|
|
322
|
+
// the service is a declaration and is applied
|
|
323
|
+
expect(response.headers.get('content-type')).toBe('text/html');
|
|
324
|
+
return [2 /*return*/];
|
|
325
|
+
}
|
|
326
|
+
});
|
|
327
|
+
}); });
|
|
242
328
|
it('keeps a debug reply text, as the body is the framed log', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
243
329
|
var dispatch, response, _a;
|
|
244
330
|
return __generator(this, function (_b) {
|
package/package.json
CHANGED
|
@@ -72,8 +72,8 @@
|
|
|
72
72
|
"dependencies": {
|
|
73
73
|
"@sasjs/adapter": "^5.1.3",
|
|
74
74
|
"@sasjs/core": "^6.1.1",
|
|
75
|
-
"@sasjs/lint": "
|
|
76
|
-
"@sasjs/utils": "^4.
|
|
75
|
+
"@sasjs/lint": "5.0.0",
|
|
76
|
+
"@sasjs/utils": "^4.7.0",
|
|
77
77
|
"axios": "1.20.0",
|
|
78
78
|
"chalk": "4.1.2",
|
|
79
79
|
"dotenv": "18.0.8",
|
|
@@ -123,5 +123,5 @@
|
|
|
123
123
|
"ws": "8.21.0",
|
|
124
124
|
"nanoid": "3.3.18"
|
|
125
125
|
},
|
|
126
|
-
"version": "6.
|
|
126
|
+
"version": "6.10.1"
|
|
127
127
|
}
|